Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
25 changes: 9 additions & 16 deletions __tests__/priceSurface.cue.tsx → __tests__/priceDisplay.ring.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
/**
* Evidence tests for the PR 1343 review findings in the staleness cue and
* the display-only ring's accessibility. Each test encodes the behavior
* the finding says the surface should have: it fails on the broken code
* and passes once the finding is fixed.
*
* F8 (ring half): the muted arc must stay distinguishable from the track.
* F9: the display-only ring is not an unnamed disabled tap stop, and the
* staleness cue reaches screen readers as a label.
* The staleness cue and the display-only ring's accessibility.
*/
jest.mock('../app/walletBackend', () => ({
__esModule: true,
Expand Down Expand Up @@ -69,7 +62,7 @@ const collect = (
(node.children ?? []).forEach(child => collect(child, hits, pick));
};

test('F8: the stale arc keeps a color of its own, distinct from the track', () => {
test('the stale arc keeps a color of its own, distinct from the track', () => {
const staleCtx = makeCtx({
zecPrice: { zecPrice: 33.33, date: Date.now() - 11 * 60_000 },
});
Expand All @@ -79,7 +72,7 @@ test('F8: the stale arc keeps a color of its own, distinct from the track', () =
expect(ring.props.ringColor).not.toBe(ring.props.trackColor);
});

test('F9: the display-only ring exposes no disabled tap stop', () => {
test('the display-only ring exposes no disabled tap stop', () => {
const freshCtx = makeCtx({
zecPrice: { zecPrice: 33.33, date: Date.now() },
});
Expand All @@ -94,23 +87,23 @@ test('F9: the display-only ring exposes no disabled tap stop', () => {
expect(disabledStops).toEqual([]);
});

test('F9: a stale price reaches screen readers as a label', () => {
test('a stale price reaches screen readers as a label', () => {
const staleCtx = makeCtx({
zecPrice: { zecPrice: 33.33, date: Date.now() - 11 * 60_000 },
});
const view = render(fetcherUi(staleCtx));
expect(view.getByLabelText('price-ring-stale')).toBeTruthy();
});

test('F9: a current price reaches screen readers as a label too', () => {
test('a current price reaches screen readers as a label too', () => {
const freshCtx = makeCtx({
zecPrice: { zecPrice: 33.33, date: Date.now() },
});
const view = render(fetcherUi(freshCtx));
expect(view.getByLabelText('price-ring-live')).toBeTruthy();
});

test('R4: the ring restarts on every refresh cycle, failed ones included', async () => {
test('the ring restarts on every refresh cycle, failed ones included', async () => {
jest.useFakeTimers();
const view = render(fetcherUi(makeCtx())); // every fetch here is refused
await jest.advanceTimersByTimeAsync(0);
Expand All @@ -126,21 +119,21 @@ test('R4: the ring restarts on every refresh cycle, failed ones included', async
jest.useRealTimers();
});

test('R5: a price that never arrived is announced as absent, not stale', async () => {
test('a price that never arrived is announced as absent, not stale', async () => {
const view = render(fetcherUi(makeCtx())); // no price has ever existed
await waitFor(() =>
expect(view.getByLabelText('price-ring-none')).toBeTruthy(),
);
});

test('N8: without the Nym consent no ring counts down to nothing', () => {
test('without the Nym opt-in no ring counts down to nothing', () => {
const view = render(fetcherUi(makeCtx({ nym: false })));
// A countdown beside a surface that will never fetch misleads; the
// unconsented state renders no ring at all.
expect(view.queryByTestId('pricefetcher.ring')).toBeNull();
});

test('P4: a refusing transport hides the ring for the same reason', () => {
test('a refusing transport hides the ring for the same reason', () => {
const view = render(
fetcherUi(
makeCtx({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
/**
* Spec tests for the ratified price cadence (ADR 0008, Consequences):
* turning Nym on, a boot, and every gate-open return from the
* background fetch at once, and each further fetch follows the last at
* a uniform random delay of five to ten minutes.
* The ratified price cadence.
*/
jest.mock('../app/walletBackend', () => ({
__esModule: true,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,5 @@
/**
* The store's observable contract: snapshot shape and identity, deps
* writes, the disposition switch, and the display-only ring,
* distilled from the reviews of PR 1343. Each test encodes the
* behavior a finding says the surface should have: it fails on the
* broken code and passes once fixed.
*
* H1: withdrawing the consent takes the ring down with the cadence.
* H2: an entry flight with no armed deadline never reads as full.
* H3: (in Send.priceCta.unit) the USD-entry derived ZEC dims when the
* price is stale.
* H4: the ready follow-up never re-arms itself.
* H5: every status key classifies under the exhaustive disposition.
* H6: the driver writes deps when an input moves, not per render.
* H7: the snapshot carries exactly its four read fields.
* H8: the snapshot keeps its identity between emits.
* H9: no any-casts in the price suites and no null in the store.
* H10: the ring is display-only.
* The store's observable contract.
*/
jest.mock('../app/walletBackend', () => ({
__esModule: true,
Expand Down Expand Up @@ -108,7 +92,7 @@ afterEach(() => {
jest.useRealTimers();
});

test('H1: withdrawing the consent takes the ring down', async () => {
test('withdrawing the opt-in takes the ring down', async () => {
jest.useFakeTimers();
price.mockResolvedValue({ price: 42, error: '' });
const setZecPrice = jest.fn();
Expand All @@ -128,7 +112,7 @@ test('H1: withdrawing the consent takes the ring down', async () => {
expect(view.queryByTestId('pricefetcher.ring')).toBeNull();
});

test('H2: an entry flight with no armed deadline never reads full', async () => {
test('an entry flight with no armed deadline never reads full', async () => {
jest.useFakeTimers();
price.mockImplementation(() => new Promise(() => {})); // in flight
const setZecPrice = jest.fn();
Expand All @@ -147,7 +131,7 @@ test('H2: an entry flight with no armed deadline never reads full', async () =>
expect(ring.props.startProgress).toBeLessThan(1);
});

test('H4: the ready follow-up never re-arms itself', async () => {
test('the ready follow-up never re-arms itself', async () => {
jest.useFakeTimers();
price.mockResolvedValue({ price: -1, error: 'refused' });
const setZecPrice = jest.fn();
Expand All @@ -168,7 +152,7 @@ test('H4: the ready follow-up never re-arms itself', async () => {
expect(price).toHaveBeenCalledTimes(4);
});

test('H5: every status key classifies under the disposition switch', () => {
test('every status key classifies under the disposition switch', () => {
const dispositions = MIXNET_STATUS_KEYS.map(key => transportDisposition(key));
dispositions.forEach(d =>
expect(['refusing', 'possibleBootstrap', 'serving']).toContain(d),
Expand All @@ -178,7 +162,7 @@ test('H5: every status key classifies under the disposition switch', () => {
expect(dispositions).toContain('serving');
});

test('H6: the driver writes deps when an input moves, not per render', async () => {
test('the driver writes deps when an input moves, not per render', async () => {
jest.useFakeTimers();
price.mockResolvedValue({ price: 42, error: '' });
const setDepsSpy = jest.spyOn(priceFetcherStore, 'setDeps');
Expand All @@ -198,7 +182,7 @@ test('H6: the driver writes deps when an input moves, not per render', async ()
setDepsSpy.mockRestore();
});

test('H7: the snapshot carries exactly its four read fields', () => {
test('the snapshot carries exactly its four read fields', () => {
expect(Object.keys(priceFetcherStore.snapshot()).sort()).toEqual([
'loading',
'nextFetchAt',
Expand All @@ -207,22 +191,22 @@ test('H7: the snapshot carries exactly its four read fields', () => {
]);
});

test('H8: the snapshot keeps its identity between emits', () => {
test('the snapshot keeps its identity between emits', () => {
const first = priceFetcherStore.snapshot();
expect(priceFetcherStore.snapshot()).toBe(first);
});

test('H9: no any-casts in the price suites and no null in the store', () => {
test('no any-casts in the price suites and no null in the store', () => {
const suites = [
'priceSurface.cadence.tsx',
'priceSurface.cue.tsx',
'priceSurface.lifecycle.tsx',
'priceSurface.trafficGuards.tsx',
'priceSurface.wedges.tsx',
'priceSurface.soleConsent.tsx',
'priceSurface.landings.tsx',
'priceSurface.storeContract.tsx',
'priceSurface.recovery.tsx',
'priceStore.cadence.tsx',
'priceDisplay.ring.tsx',
'priceStore.lifecycle.tsx',
'priceStore.trafficGuards.tsx',
'priceStore.wedgeGuards.tsx',
'priceStore.optIn.tsx',
'priceStore.nativeCall.tsx',
'priceStore.contract.tsx',
'priceStore.recovery.tsx',
'Send.priceCta.unit.tsx',
'PriceFetcher.snapshot.tsx',
];
Expand All @@ -240,7 +224,7 @@ test('H9: no any-casts in the price suites and no null in the store', () => {
expect(new RegExp('\\bnul' + 'l\\b').test(store)).toBe(false);
});

test('H10: the ring is display-only', () => {
test('the ring is display-only', () => {
const ringSource = fs.readFileSync(
path.join(__dirname, '../components/Components/QuoteRefreshRing.tsx'),
'utf8',
Expand Down
Loading
Loading