From 923dc7f92c62fe06f5b2cd16773bccbe1eecfb17 Mon Sep 17 00:00:00 2001 From: Jeff Repanich Date: Mon, 24 Aug 2026 09:58:03 -0400 Subject: [PATCH] test: cover adversarial runtime concurrency --- tests/jsdom/runtime/hardening-matrix.test.tsx | 185 ++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 tests/jsdom/runtime/hardening-matrix.test.tsx diff --git a/tests/jsdom/runtime/hardening-matrix.test.tsx b/tests/jsdom/runtime/hardening-matrix.test.tsx new file mode 100644 index 00000000..3f904a3d --- /dev/null +++ b/tests/jsdom/runtime/hardening-matrix.test.tsx @@ -0,0 +1,185 @@ +import { describe, expect, it } from 'vite-plus/test'; +import { cleanupApp } from '../../../src/boot'; +import { For } from '../../../src/control'; +import { state, type State } from '../../../src/index'; +import { resource } from '../../../src/resources'; +import { globalScheduler, Scheduler } from '../../../src/runtime/scheduler'; +import { createIsland } from '../../../test-utils/render/create-island'; +import { + createTestContainer, + flushScheduler, +} from '../../../test-utils/render/test-renderer'; + +async function settleResourceWork(): Promise { + await Promise.resolve(); + await Promise.resolve(); + flushScheduler(); +} + +describe('core hardening matrix', () => { + it('should settle overlapping and already-superseded flush waiters', async () => { + const scheduler = new Scheduler(); + scheduler.enqueue(() => {}); + const first = scheduler.waitForFlush(1); + const second = scheduler.waitForFlush(2); + + scheduler.flush(); + await first; + let secondSettled = false; + void second.then(() => { + secondSettled = true; + }); + await Promise.resolve(); + expect(secondSettled).toBe(false); + + scheduler.enqueue(() => {}); + scheduler.flush(); + await second; + await expect(scheduler.waitForFlush(1)).resolves.toBeUndefined(); + }); + + it('should serialize a state write made reentrantly inside another updater', () => { + const { container, cleanup } = createTestContainer(); + let first!: State; + let second!: State; + + try { + createIsland({ + root: container, + component: () => { + first = state(1); + second = state(10); + return {`${String(first())}:${String(second())}`}; + }, + }); + + first.set((previous) => { + second.set((other) => other + 5); + return previous + 1; + }); + flushScheduler(); + + expect(container.textContent).toBe('2:15'); + } finally { + cleanup(); + } + }); + + it('should make state work inert when its owner unmounts during an active flush', () => { + const { container, cleanup } = createTestContainer(); + let value!: State; + let renders = 0; + + try { + createIsland({ + root: container, + component: () => { + renders += 1; + value = state(0); + return {String(value())}; + }, + }); + + globalScheduler.enqueue(() => cleanupApp(container)); + globalScheduler.enqueue(() => value.set(1)); + expect(() => flushScheduler()).not.toThrow(); + expect(renders).toBe(1); + expect(globalScheduler.getState().queueLength).toBe(0); + } finally { + cleanup(); + } + }); + + it('should isolate two resources sharing a changing dependency across resolution races', async () => { + const { container, cleanup } = createTestContainer(); + const firstResolvers = new Map void>(); + const secondResolvers = new Map void>(); + let dependency!: State; + + try { + createIsland({ + root: container, + component: () => { + dependency = state('old'); + const key = dependency(); + const first = resource( + () => + new Promise((resolve) => { + firstResolvers.set(key, resolve); + }), + [key] + ); + const second = resource( + () => + new Promise((resolve) => { + secondResolvers.set(key, resolve); + }), + [key] + ); + return ( + {`${first.value ?? '-'}:${second.value ?? '-'}`} + ); + }, + }); + await settleResourceWork(); + + dependency.set('new'); + flushScheduler(); + await settleResourceWork(); + secondResolvers.get('new')!('second:new'); + firstResolvers.get('new')!('first:new'); + await settleResourceWork(); + expect(container.textContent).toBe('first:new:second:new'); + + firstResolvers.get('old')!('first:old'); + secondResolvers.get('old')!('second:old'); + await settleResourceWork(); + expect(container.textContent).toBe('first:new:second:new'); + } finally { + cleanup(); + } + }); + + it('should reconcile two independent For lists in the same flush', () => { + const { container, cleanup } = createTestContainer(); + let first!: State; + let second!: State; + + try { + createIsland({ + root: container, + component: () => { + first = state(['a', 'b']); + second = state(['x', 'y']); + return ( +
+
    + first()} by={(item) => item}> + {(item) =>
  1. {item}
  2. } +
    +
+
    + second()} by={(item) => item}> + {(item) =>
  1. {item}
  2. } +
    +
+
+ ); + }, + }); + + first.set(['b', 'c', 'a']); + second.set(['y', 'z']); + flushScheduler(); + + const text = (selector: string) => + Array.from(container.querySelectorAll(`${selector} li`)).map( + (item) => item.textContent + ); + expect(text('[data-list="first"]')).toEqual(['b', 'c', 'a']); + expect(text('[data-list="second"]')).toEqual(['y', 'z']); + } finally { + cleanup(); + } + }); +});