Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 16 additions & 7 deletions app/__tests__/lib/priceStore-poll.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,24 @@
*/
import { describe, it, expect, vi, afterEach } from "vitest";

// Capture the WS message listener so tests can inject "live" ticks without
// a real socket. subscribeChannel/onMessage mirror WsManagerHandle's shape.
const messageListeners: Array<(data: unknown) => void> = [];
// Capture the WS message listeners so tests can inject "live" ticks without a
// real socket. priceStore subscribes via `onMessageForChannel(slab, listener)`,
// which dispatches ONLY to listeners registered for that channel — so the mock
// keys listeners by channel rather than holding one global list. A global list
// would deliver every tick to every slab, which the real manager never does.
const channelListeners = new Map<string, Array<(data: unknown) => void>>();
vi.mock("@/lib/priceStore/wsManager", () => ({
getWsManager: () => ({
subscribeChannel: () => () => {},
onMessage: (l: (data: unknown) => void) => {
messageListeners.push(l);
return () => {};
onMessage: () => () => {},
onMessageForChannel: (channel: string, l: (data: unknown) => void) => {
const list = channelListeners.get(channel) ?? [];
list.push(l);
channelListeners.set(channel, list);
return () => {
const cur = channelListeners.get(channel);
if (cur) channelListeners.set(channel, cur.filter((x) => x !== l));
};
},
onStatusChange: () => () => {},
}),
Expand All @@ -31,7 +40,7 @@ import {
* visibilitychange handler flushes pending ticks synchronously — same
* path the real store uses when a backgrounded tab returns). */
function deliverLiveTick(slab: string, price: number) {
for (const l of messageListeners) l({ type: "price", slab, price });
for (const l of channelListeners.get(slab) ?? []) l({ type: "price", slab, price });
document.dispatchEvent(new Event("visibilitychange"));
}

Expand Down
22 changes: 20 additions & 2 deletions app/__tests__/lib/v17-matcher-state.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,23 +62,41 @@ describe('v17 matcher-state helpers', () => {
expect(inspectV17MatcherContext(data, publicKey(21))).toBe('uninitialized');
});

// The 16-byte VAMM magic occupies CTX_VAMM_OFFSET; the delegate starts
// immediately after it. Writing at CTX_VAMM_OFFSET itself lands on the magic
// and leaves the real delegate slot half-zeroed — which is why the 'invalid'
// case below used to pass for the wrong reason. Matches the offset already
// used by __tests__/hooks/useCreateMarket.v17-matcher-{resume,recovery}.test.ts.
const DELEGATE_OFFSET = CTX_VAMM_OFFSET + 16;

it('classifies a context containing the expected delegate as initialized', () => {
const data = new Uint8Array(MATCHER_CONTEXT_LEN);
const delegate = publicKey(22);

data.set(delegate.toBytes(), CTX_VAMM_OFFSET);
data.set(delegate.toBytes(), DELEGATE_OFFSET);

expect(inspectV17MatcherContext(data, delegate)).toBe('initialized');
});

it('rejects a context bound to another delegate', () => {
const data = new Uint8Array(MATCHER_CONTEXT_LEN);

data.set(publicKey(23).toBytes(), CTX_VAMM_OFFSET);
data.set(publicKey(23).toBytes(), DELEGATE_OFFSET);

expect(inspectV17MatcherContext(data, publicKey(24))).toBe('invalid');
});

it('does not read the delegate from the magic bytes at CTX_VAMM_OFFSET', () => {
// Guards the exact regression the reader's comment describes: a context
// whose delegate slot is untouched must read as uninitialized, no matter
// what the magic region contains.
const data = new Uint8Array(MATCHER_CONTEXT_LEN);
data.set(publicKey(25).toBytes(), CTX_VAMM_OFFSET);
data.fill(0, DELEGATE_OFFSET, DELEGATE_OFFSET + 32);

expect(inspectV17MatcherContext(data, publicKey(25))).toBe('uninitialized');
});

it('rejects an undersized matcher context', () => {
const data = new Uint8Array(MATCHER_CONTEXT_LEN - 1);

Expand Down
25 changes: 21 additions & 4 deletions app/__tests__/unit/gh1654-market-info-bar-visibility.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* 1. data-testid="market-info-bar" attribute present
* 2. sticky positioning class
* 3. No `hidden` class on the wrapper (visible on all breakpoints)
* 4. MarketLogo is imported and rendered
* 4. A market logo still reaches the bar (now via MarketSwitcher)
*/

import { readFileSync } from "fs";
Expand All @@ -20,6 +20,11 @@ const pageSource = readFileSync(
"utf-8"
);

const switcherSource = readFileSync(
join(__dirname, "../../components/trade/MarketSwitcher.tsx"),
"utf-8"
);

describe("GH#1654 — MarketInfoBar visibility", () => {
test("has data-testid attribute", () => {
expect(barSource).toContain('data-testid="market-info-bar"');
Expand All @@ -29,9 +34,21 @@ describe("GH#1654 — MarketInfoBar visibility", () => {
expect(barSource).toContain("sticky");
});

test("imports MarketLogo", () => {
expect(barSource).toContain("MarketLogo");
expect(barSource).toContain("@/components/market/MarketLogo");
// MarketInfoBar no longer imports MarketLogo directly — the logo moved into
// MarketSwitcher, which the bar renders. The GH#1654 guarantee ("the info bar
// shows a market logo") is unchanged, so follow the composition rather than
// asserting on an import that legitimately moved one level down.
test("renders MarketSwitcher with the logo props", () => {
expect(barSource).toContain("@/components/trade/MarketSwitcher");
const usage = barSource.match(/<MarketSwitcher[^>]*>/)?.[0] ?? "";
expect(usage).toContain("logoUrl");
expect(usage).toContain("mintAddress");
expect(usage).toContain("symbol");
});

test("MarketSwitcher renders MarketLogo", () => {
expect(switcherSource).toContain("@/components/market/MarketLogo");
expect(switcherSource).toMatch(/<MarketLogo[^>]*mintAddress/);
});

test("page renders MarketInfoBar without hidden wrapper", () => {
Expand Down
Loading