mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 21:46:49 +00:00
7a7903a799
The offline core only works when the views go through it: component, feature hook, store, repo, then api or Dexie, with writes through the mutation queue. A view that imports src/api/ skips the cache, the optimistic write and the idempotent replay. client/CLAUDE.md said so and noted that no ratchet held the line, and the number of view files importing api/client grew from 161 to 190 in two months. lint:offline lists the 213 files under components/, mobile/, pages/ and hooks/ that import anything from src/api/ for more than types (api/client, the websocket, the per-domain modules) in scripts/offline-baseline.json. No other view file may, and a listed file that stops leaves the list through --update, so the next file at that path does not inherit the exemption. Type-only imports do not count: they carry no request. client/CLAUDE.md describes the rule next to the data-flow section, including that an online-only domain puts its calls in a store or repo.
72 lines
3.3 KiB
TypeScript
72 lines
3.3 KiB
TypeScript
import { readFileSync } from 'node:fs';
|
|
import { afterEach, describe, expect, it } from 'vitest';
|
|
import { bypasses, check } from '../../../scripts/lib/offline.mjs';
|
|
import { RatchetError } from '../../../scripts/lib/ratchet.mjs';
|
|
import { ratchetTree, type RatchetTree } from '../../helpers/ratchetFixture';
|
|
|
|
let tree: RatchetTree;
|
|
afterEach(() => tree?.remove());
|
|
|
|
const BASELINE = 'scripts/offline-baseline.json';
|
|
const DIRECT = "import { tripsApi } from '../../api/client'\n";
|
|
|
|
function run(baseline: string[], files: Record<string, string>, update = false) {
|
|
tree = ratchetTree({
|
|
[BASELINE]: JSON.stringify(baseline),
|
|
'src/api/client.ts': 'export const tripsApi = {}\n',
|
|
...files,
|
|
});
|
|
return check({ root: tree.root, baselinePath: tree.path(BASELINE), update, ...tree.out });
|
|
}
|
|
|
|
describe('lint:offline', () => {
|
|
it('OFFLINE-001: finds view files that import src/api/ for more than types', () => {
|
|
tree = ratchetTree({
|
|
'src/components/A/A.tsx': DIRECT,
|
|
'src/mobile/M.tsx': "import { connect } from '../api/websocket'\n",
|
|
'src/pages/p/usePage.ts': "const load = () => import('../../api/client')\n",
|
|
'src/hooks/useTypes.ts': "import type { Trip } from '../api/client'\n",
|
|
'src/hooks/useMixed.ts': "import { type Trip, tripsApi } from '../api/client'\n",
|
|
'src/store/s.ts': "import { tripsApi } from '../api/client'\n",
|
|
'src/repo/r.ts': "import { tripsApi } from '../api/client'\n",
|
|
'src/components/A/A.test.tsx': DIRECT,
|
|
'src/components/B.tsx': "import { tripRepo } from '../repo/tripRepo'\n",
|
|
});
|
|
expect(bypasses(tree.root)).toEqual({
|
|
'src/components/A/A.tsx': ["1: '../../api/client'"],
|
|
'src/hooks/useMixed.ts': ["1: '../api/client'"],
|
|
'src/mobile/M.tsx': ["1: '../api/websocket'"],
|
|
'src/pages/p/usePage.ts': ["1: '../../api/client'"],
|
|
});
|
|
});
|
|
|
|
it('OFFLINE-002: passes the listed files and fails a new one', () => {
|
|
expect(run(['src/components/A/A.tsx'], { 'src/components/A/A.tsx': DIRECT })).toBe(0);
|
|
tree.remove();
|
|
expect(
|
|
run(['src/components/A/A.tsx'], { 'src/components/A/A.tsx': DIRECT, 'src/components/B/B.tsx': DIRECT })
|
|
).toBe(1);
|
|
expect(tree.error.join('\n')).toMatch(/src\/components\/B\/B\.tsx imports src\/api\/ directly/);
|
|
expect(tree.error.join('\n')).not.toMatch(/A\.tsx imports/);
|
|
});
|
|
|
|
it('OFFLINE-003: fails a listed file that stopped, until --update takes it off', () => {
|
|
const files = { 'src/components/A/A.tsx': "import { tripRepo } from '../../repo/tripRepo'\n" };
|
|
expect(run(['src/components/A/A.tsx'], files)).toBe(1);
|
|
expect(tree.error.join('\n')).toMatch(/A\.tsx is listed .* no longer imports src\/api\//);
|
|
tree.remove();
|
|
expect(run(['src/components/A/A.tsx'], files, true)).toBe(0);
|
|
expect(JSON.parse(readFileSync(tree.path(BASELINE), 'utf8'))).toEqual([]);
|
|
});
|
|
|
|
it('OFFLINE-004: --update never puts a file on the list', () => {
|
|
expect(run([], { 'src/hooks/useX.ts': "import { tripsApi } from '../api/client'\n" }, true)).toBe(1);
|
|
expect(JSON.parse(readFileSync(tree.path(BASELINE), 'utf8'))).toEqual([]);
|
|
});
|
|
|
|
it('OFFLINE-005: a broken baseline stops the check', () => {
|
|
tree = ratchetTree({ [BASELINE]: '{"src/a.ts": 1}', 'src/a.ts': '' });
|
|
expect(() => check({ root: tree.root, baselinePath: tree.path(BASELINE), ...tree.out })).toThrow(RatchetError);
|
|
});
|
|
});
|