Skip to content
Merged
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
27 changes: 27 additions & 0 deletions packages/viewer/.vscode/keybindings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[
{
"key": "cmd+shift+t",
"command": "workbench.action.tasks.runTask",
"args": "Run Tests"
},
{
"key": "cmd+shift+w",
"command": "workbench.action.tasks.runTask",
"args": "Watch Tests"
},
{
"key": "cmd+shift+c",
"command": "workbench.action.tasks.runTask",
"args": "Test with Coverage"
},
{
"key": "cmd+shift+v",
"command": "workbench.action.tasks.runTask",
"args": "View Coverage Report"
},
{
"key": "cmd+shift+y",
"command": "workbench.action.tasks.runTask",
"args": "Open Cypress"
}
]
44 changes: 44 additions & 0 deletions packages/viewer/.vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Current Test File",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/node_modules/vitest/vitest.mjs",
"args": ["run", "${relativeFile}"],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"cwd": "${workspaceFolder}",
"env": {
"NODE_ENV": "test"
}
},
{
"name": "Debug All Tests",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/node_modules/vitest/vitest.mjs",
"args": ["run"],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"cwd": "${workspaceFolder}",
"env": {
"NODE_ENV": "test"
}
},
{
"name": "Debug Tests in Watch Mode",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/node_modules/vitest/vitest.mjs",
"args": ["--watch"],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"cwd": "${workspaceFolder}",
"env": {
"NODE_ENV": "test"
}
}
]
}
13 changes: 13 additions & 0 deletions packages/viewer/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"vitest.enable": true,
"vitest.workspaceConfig": "./vite.config.js",
"vitest.commandLine": "npm run test:watch",
"testing.automaticallyOpenPeekView": "failureInVisibleDocument",
"testing.defaultGutterClickAction": "run",
"testing.followRunningTest": "true",
"testing.openTesting": "neverOpen",
"files.watcherExclude": {
"**/coverage/**": true
},
"testing.automaticallyOpenTestResults": "neverOpen"
}
113 changes: 113 additions & 0 deletions packages/viewer/.vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "Run Tests",
"type": "shell",
"command": "npm run test",
"group": "test",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared",
"showReuseMessage": true,
"clear": false
},
"problemMatcher": []
},
{
"label": "Watch Tests",
"type": "shell",
"command": "npm run test:watch",
"group": "test",
"isBackground": true,
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "dedicated",
"showReuseMessage": true,
"clear": false
},
"problemMatcher": []
},
{
"label": "Test with Coverage",
"type": "shell",
"command": "npm run test:cov",
"group": "test",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared",
"showReuseMessage": true,
"clear": false
},
"problemMatcher": []
},
{
"label": "View Coverage Report",
"type": "shell",
"command": "open",
"args": ["coverage/index.html"],
"group": "test",
"dependsOn": "Test with Coverage",
"presentation": {
"echo": true,
"reveal": "silent",
"focus": false,
"panel": "shared",
"showReuseMessage": false,
"clear": false
},
"problemMatcher": []
},
{
"label": "Open Cypress",
"type": "shell",
"command": "npm run cy:open",
"group": "test",
"isBackground": true,
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "dedicated",
"showReuseMessage": true,
"clear": false
},
"problemMatcher": []
},
{
"label": "Run Single Test File",
"type": "shell",
"command": "npx vitest run ${relativeFile}",
"group": "test",
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared",
"showReuseMessage": true,
"clear": false
},
"problemMatcher": []
},
{
"label": "Clean Coverage",
"type": "shell",
"command": "rm -rf coverage",
"group": "build",
"presentation": {
"echo": true,
"reveal": "silent",
"focus": false,
"panel": "shared",
"showReuseMessage": false,
"clear": false
}
}
]
}
189 changes: 189 additions & 0 deletions packages/viewer/src/context.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import { describe, expect, it, vi, type MockedFunction } from 'vitest'
import { initialize } from './context'
import { writable, type Writable } from 'svelte/store'
import * as Errors from './errors'
import type * as Gateway from './gateway'
import type { Local } from './repositories'
import { renderer } from './parser'
import { type Prestore } from './stores'
import { property, space, theorem } from './__test__/factories'
import { get } from 'svelte/store'
import type { Result } from './gateway'

type InitializeParams = Parameters<typeof initialize>[0]

type SetupParams = {
local: Partial<Prestore>
remote: Result
} & Pick<InitializeParams, 'showDev' | 'source'>

describe(initialize, () => {
let db: Local<Prestore> & {
subscribe: MockedFunction<Local<Prestore>['subscribe']>
}
let errorHandler: MockedFunction<Errors.Handler>
let gateway: MockedFunction<Gateway.Sync>
let mockRenderer: typeof renderer

function setup(args: Partial<SetupParams> = {}) {
vi.clearAllMocks()

errorHandler = vi.fn()
mockRenderer = vi.fn()

const prestore: Prestore = {
spaces: [],
properties: [],
theorems: [],
traits: [],
deduction: { checked: new Set(), all: new Set() },
source: { host: 'example.com', branch: 'main' },
sync: undefined,
...args.local,
}

db = {
load: () => prestore,
subscribe: vi.fn(),
}

gateway = vi.fn(async (_host: string, _branch: string) => args.remote)

return initialize({
db,
errorHandler,
gateway,
typesetter: mockRenderer,
showDev: args.showDev || false,
source: args.source || {},
})
}

it('should create a context with default dependencies', () => {
const { showDev, errorHandler } = setup()

expect(showDev).toBe(false)
expect(errorHandler).toEqual(errorHandler)
})

it('use the provided source', () => {
const { source } = setup({
source: { host: 'default.com', branch: 'master' },
})

expect(get(source)).toEqual({ host: 'default.com', branch: 'master' })
})

it('should trigger sync when local sync state is undefined', () => {
setup({
local: {
source: { host: 'example.com', branch: 'main' },
sync: undefined,
},
})

expect(gateway).toHaveBeenCalledWith('example.com', 'main', undefined)
})

it('persists remote sync state to the local db', async () => {
const context = setup({
local: {
source: { host: 'example.com', branch: 'main' },
sync: undefined,
},
remote: {
spaces: [space({ id: 123 })],
properties: [property({ id: 456 })],
theorems: [],
traits: [],
etag: 'etag',
sha: 'sha',
},
})

await context.loaded()

const { spaces, properties, sync } = db.subscribe.mock.calls[0][0]

expect(get(spaces).map(s => s.id)).toEqual([123])
expect(get(properties).map(p => p.id)).toEqual([456])
expect(get(sync)).toEqual(
expect.objectContaining({
kind: 'fetched',
value: { etag: 'etag', sha: 'sha' },
}),
)
})

it('runs deductions', async () => {
const context = setup({
local: {
spaces: [space({ id: 1 })],
properties: [property({ id: 1 }), property({ id: 2 })],
traits: [
{
asserted: true,
space: 1,
property: 1,
value: true,
description: '',
refs: [],
},
],
theorems: [
theorem({
id: 1,
when: { kind: 'atom', property: 1, value: true },
then: { kind: 'atom', property: 2, value: true },
}),
],
},
})

await context.checked('S1')

const { trait, proof } = get(context.traits).lookup({
spaceId: 'S1',
propertyId: 'P2',
theorems: get(context.theorems),
})!

expect(trait?.value).toEqual(true)
expect(proof?.theorems.map(t => t.id)).toEqual([1])
})

it('can wait for a space to be checked', async () => {
const { checked } = setup({
local: {
spaces: [space({ id: 123 })],
},
})

expect(checked('S123')).resolves.toBeUndefined()
})

describe('load', () => {
let store: Writable<number>

it('resolves when lookup finds value', async () => {
store = writable(0)
const context = setup()

const loadPromise = context.load(store, n => (n > 10 ? n : false))

store.set(5)
store.set(13)

await expect(loadPromise).resolves.toBe(13)
})

it('rejects when until promise resolves first', async () => {
store = writable(0)
const context = setup()

const loadPromise = context.load(store, n => n > 0, Promise.resolve())

await expect(loadPromise).rejects.toBeUndefined()
})
})
})
2 changes: 1 addition & 1 deletion packages/viewer/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export type { Context } from './context/types'

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

import type { Context } from '@/context/types'
import { trace } from '@/debug'
Expand Down
Loading
Loading