Skip to content

Commit 7235558

Browse files
authored
ci: add tests covering top-level context setup (#224)
* ci: add tests covering top-level context setup The major addition here is tests covering the integration between the top-level app Context initialization and the Store loading from the Local<Prestore> and Gateway. These can be expanded, but offer a decent enough regression suite for the top-level loading behavior. This commit includes a few minor QoL additions: - setup vscode / copilot support for running tests via tasks - remove an unused import - make some parameters optional when a sensible default is available * ci: add test coverage for the deduction store
1 parent c00537c commit 7235558

File tree

10 files changed

+698
-8
lines changed

10 files changed

+698
-8
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
[
2+
{
3+
"key": "cmd+shift+t",
4+
"command": "workbench.action.tasks.runTask",
5+
"args": "Run Tests"
6+
},
7+
{
8+
"key": "cmd+shift+w",
9+
"command": "workbench.action.tasks.runTask",
10+
"args": "Watch Tests"
11+
},
12+
{
13+
"key": "cmd+shift+c",
14+
"command": "workbench.action.tasks.runTask",
15+
"args": "Test with Coverage"
16+
},
17+
{
18+
"key": "cmd+shift+v",
19+
"command": "workbench.action.tasks.runTask",
20+
"args": "View Coverage Report"
21+
},
22+
{
23+
"key": "cmd+shift+y",
24+
"command": "workbench.action.tasks.runTask",
25+
"args": "Open Cypress"
26+
}
27+
]
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
{
2+
"version": "0.2.0",
3+
"configurations": [
4+
{
5+
"name": "Debug Current Test File",
6+
"type": "node",
7+
"request": "launch",
8+
"program": "${workspaceFolder}/node_modules/vitest/vitest.mjs",
9+
"args": ["run", "${relativeFile}"],
10+
"console": "integratedTerminal",
11+
"internalConsoleOptions": "neverOpen",
12+
"cwd": "${workspaceFolder}",
13+
"env": {
14+
"NODE_ENV": "test"
15+
}
16+
},
17+
{
18+
"name": "Debug All Tests",
19+
"type": "node",
20+
"request": "launch",
21+
"program": "${workspaceFolder}/node_modules/vitest/vitest.mjs",
22+
"args": ["run"],
23+
"console": "integratedTerminal",
24+
"internalConsoleOptions": "neverOpen",
25+
"cwd": "${workspaceFolder}",
26+
"env": {
27+
"NODE_ENV": "test"
28+
}
29+
},
30+
{
31+
"name": "Debug Tests in Watch Mode",
32+
"type": "node",
33+
"request": "launch",
34+
"program": "${workspaceFolder}/node_modules/vitest/vitest.mjs",
35+
"args": ["--watch"],
36+
"console": "integratedTerminal",
37+
"internalConsoleOptions": "neverOpen",
38+
"cwd": "${workspaceFolder}",
39+
"env": {
40+
"NODE_ENV": "test"
41+
}
42+
}
43+
]
44+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"vitest.enable": true,
3+
"vitest.workspaceConfig": "./vite.config.js",
4+
"vitest.commandLine": "npm run test:watch",
5+
"testing.automaticallyOpenPeekView": "failureInVisibleDocument",
6+
"testing.defaultGutterClickAction": "run",
7+
"testing.followRunningTest": "true",
8+
"testing.openTesting": "neverOpen",
9+
"files.watcherExclude": {
10+
"**/coverage/**": true
11+
},
12+
"testing.automaticallyOpenTestResults": "neverOpen"
13+
}

packages/viewer/.vscode/tasks.json

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
{
2+
"version": "2.0.0",
3+
"tasks": [
4+
{
5+
"label": "Run Tests",
6+
"type": "shell",
7+
"command": "npm run test",
8+
"group": "test",
9+
"presentation": {
10+
"echo": true,
11+
"reveal": "always",
12+
"focus": false,
13+
"panel": "shared",
14+
"showReuseMessage": true,
15+
"clear": false
16+
},
17+
"problemMatcher": []
18+
},
19+
{
20+
"label": "Watch Tests",
21+
"type": "shell",
22+
"command": "npm run test:watch",
23+
"group": "test",
24+
"isBackground": true,
25+
"presentation": {
26+
"echo": true,
27+
"reveal": "always",
28+
"focus": false,
29+
"panel": "dedicated",
30+
"showReuseMessage": true,
31+
"clear": false
32+
},
33+
"problemMatcher": []
34+
},
35+
{
36+
"label": "Test with Coverage",
37+
"type": "shell",
38+
"command": "npm run test:cov",
39+
"group": "test",
40+
"presentation": {
41+
"echo": true,
42+
"reveal": "always",
43+
"focus": false,
44+
"panel": "shared",
45+
"showReuseMessage": true,
46+
"clear": false
47+
},
48+
"problemMatcher": []
49+
},
50+
{
51+
"label": "View Coverage Report",
52+
"type": "shell",
53+
"command": "open",
54+
"args": ["coverage/index.html"],
55+
"group": "test",
56+
"dependsOn": "Test with Coverage",
57+
"presentation": {
58+
"echo": true,
59+
"reveal": "silent",
60+
"focus": false,
61+
"panel": "shared",
62+
"showReuseMessage": false,
63+
"clear": false
64+
},
65+
"problemMatcher": []
66+
},
67+
{
68+
"label": "Open Cypress",
69+
"type": "shell",
70+
"command": "npm run cy:open",
71+
"group": "test",
72+
"isBackground": true,
73+
"presentation": {
74+
"echo": true,
75+
"reveal": "always",
76+
"focus": false,
77+
"panel": "dedicated",
78+
"showReuseMessage": true,
79+
"clear": false
80+
},
81+
"problemMatcher": []
82+
},
83+
{
84+
"label": "Run Single Test File",
85+
"type": "shell",
86+
"command": "npx vitest run ${relativeFile}",
87+
"group": "test",
88+
"presentation": {
89+
"echo": true,
90+
"reveal": "always",
91+
"focus": false,
92+
"panel": "shared",
93+
"showReuseMessage": true,
94+
"clear": false
95+
},
96+
"problemMatcher": []
97+
},
98+
{
99+
"label": "Clean Coverage",
100+
"type": "shell",
101+
"command": "rm -rf coverage",
102+
"group": "build",
103+
"presentation": {
104+
"echo": true,
105+
"reveal": "silent",
106+
"focus": false,
107+
"panel": "shared",
108+
"showReuseMessage": false,
109+
"clear": false
110+
}
111+
}
112+
]
113+
}
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
import { describe, expect, it, vi, type MockedFunction } from 'vitest'
2+
import { initialize } from './context'
3+
import { writable, type Writable } from 'svelte/store'
4+
import * as Errors from './errors'
5+
import type * as Gateway from './gateway'
6+
import type { Local } from './repositories'
7+
import { renderer } from './parser'
8+
import { type Prestore } from './stores'
9+
import { property, space, theorem } from './__test__/factories'
10+
import { get } from 'svelte/store'
11+
import type { Result } from './gateway'
12+
13+
type InitializeParams = Parameters<typeof initialize>[0]
14+
15+
type SetupParams = {
16+
local: Partial<Prestore>
17+
remote: Result
18+
} & Pick<InitializeParams, 'showDev' | 'source'>
19+
20+
describe(initialize, () => {
21+
let db: Local<Prestore> & {
22+
subscribe: MockedFunction<Local<Prestore>['subscribe']>
23+
}
24+
let errorHandler: MockedFunction<Errors.Handler>
25+
let gateway: MockedFunction<Gateway.Sync>
26+
let mockRenderer: typeof renderer
27+
28+
function setup(args: Partial<SetupParams> = {}) {
29+
vi.clearAllMocks()
30+
31+
errorHandler = vi.fn()
32+
mockRenderer = vi.fn()
33+
34+
const prestore: Prestore = {
35+
spaces: [],
36+
properties: [],
37+
theorems: [],
38+
traits: [],
39+
deduction: { checked: new Set(), all: new Set() },
40+
source: { host: 'example.com', branch: 'main' },
41+
sync: undefined,
42+
...args.local,
43+
}
44+
45+
db = {
46+
load: () => prestore,
47+
subscribe: vi.fn(),
48+
}
49+
50+
gateway = vi.fn(async (_host: string, _branch: string) => args.remote)
51+
52+
return initialize({
53+
db,
54+
errorHandler,
55+
gateway,
56+
typesetter: mockRenderer,
57+
showDev: args.showDev || false,
58+
source: args.source || {},
59+
})
60+
}
61+
62+
it('should create a context with default dependencies', () => {
63+
const { showDev, errorHandler } = setup()
64+
65+
expect(showDev).toBe(false)
66+
expect(errorHandler).toEqual(errorHandler)
67+
})
68+
69+
it('use the provided source', () => {
70+
const { source } = setup({
71+
source: { host: 'default.com', branch: 'master' },
72+
})
73+
74+
expect(get(source)).toEqual({ host: 'default.com', branch: 'master' })
75+
})
76+
77+
it('should trigger sync when local sync state is undefined', () => {
78+
setup({
79+
local: {
80+
source: { host: 'example.com', branch: 'main' },
81+
sync: undefined,
82+
},
83+
})
84+
85+
expect(gateway).toHaveBeenCalledWith('example.com', 'main', undefined)
86+
})
87+
88+
it('persists remote sync state to the local db', async () => {
89+
const context = setup({
90+
local: {
91+
source: { host: 'example.com', branch: 'main' },
92+
sync: undefined,
93+
},
94+
remote: {
95+
spaces: [space({ id: 123 })],
96+
properties: [property({ id: 456 })],
97+
theorems: [],
98+
traits: [],
99+
etag: 'etag',
100+
sha: 'sha',
101+
},
102+
})
103+
104+
await context.loaded()
105+
106+
const { spaces, properties, sync } = db.subscribe.mock.calls[0][0]
107+
108+
expect(get(spaces).map(s => s.id)).toEqual([123])
109+
expect(get(properties).map(p => p.id)).toEqual([456])
110+
expect(get(sync)).toEqual(
111+
expect.objectContaining({
112+
kind: 'fetched',
113+
value: { etag: 'etag', sha: 'sha' },
114+
}),
115+
)
116+
})
117+
118+
it('runs deductions', async () => {
119+
const context = setup({
120+
local: {
121+
spaces: [space({ id: 1 })],
122+
properties: [property({ id: 1 }), property({ id: 2 })],
123+
traits: [
124+
{
125+
asserted: true,
126+
space: 1,
127+
property: 1,
128+
value: true,
129+
description: '',
130+
refs: [],
131+
},
132+
],
133+
theorems: [
134+
theorem({
135+
id: 1,
136+
when: { kind: 'atom', property: 1, value: true },
137+
then: { kind: 'atom', property: 2, value: true },
138+
}),
139+
],
140+
},
141+
})
142+
143+
await context.checked('S1')
144+
145+
const { trait, proof } = get(context.traits).lookup({
146+
spaceId: 'S1',
147+
propertyId: 'P2',
148+
theorems: get(context.theorems),
149+
})!
150+
151+
expect(trait?.value).toEqual(true)
152+
expect(proof?.theorems.map(t => t.id)).toEqual([1])
153+
})
154+
155+
it('can wait for a space to be checked', async () => {
156+
const { checked } = setup({
157+
local: {
158+
spaces: [space({ id: 123 })],
159+
},
160+
})
161+
162+
expect(checked('S123')).resolves.toBeUndefined()
163+
})
164+
165+
describe('load', () => {
166+
let store: Writable<number>
167+
168+
it('resolves when lookup finds value', async () => {
169+
store = writable(0)
170+
const context = setup()
171+
172+
const loadPromise = context.load(store, n => (n > 10 ? n : false))
173+
174+
store.set(5)
175+
store.set(13)
176+
177+
await expect(loadPromise).resolves.toBe(13)
178+
})
179+
180+
it('rejects when until promise resolves first', async () => {
181+
store = writable(0)
182+
const context = setup()
183+
184+
const loadPromise = context.load(store, n => n > 0, Promise.resolve())
185+
186+
await expect(loadPromise).rejects.toBeUndefined()
187+
})
188+
})
189+
})

packages/viewer/src/context.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ export type { Context } from './context/types'
22

33
import { formula as F } from '@pi-base/core'
44
import { getContext, setContext } from 'svelte'
5-
import { derived, get, type Readable } from 'svelte/store'
5+
import { derived, type Readable } from 'svelte/store'
66

77
import type { Context } from '@/context/types'
88
import { trace } from '@/debug'

0 commit comments

Comments
 (0)