diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 3c87f319..d7c5ca2e 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -43,10 +43,24 @@ jobs: working-directory: packages/core run: pnpm build + - name: Build viewer + working-directory: packages/viewer + run: | + VITE_BUNDLE_HOST=http://localhost:4173 pnpm run build + + - name: Copy fixtures + working-directory: packages/viewer + # The viewer build is configured to load data from the local server's + # static assets, so we populate the main branch location with fixture + # data, for deterministic testing. + run: | + mkdir -p dist/refs/heads + cp cypress/fixtures/main.min.json dist/refs/heads/main.json + - name: Cypress run uses: cypress-io/github-action@v5 with: browser: chrome build: echo "Start runs build" - command: pnpm --filter viewer run cy:run - start: pnpm --filter viewer run dev + command: pnpm --filter viewer run cy:run --config baseUrl=http://localhost:4173 + start: pnpm --filter viewer run preview diff --git a/packages/viewer/.github/workflows/ci-cd.yaml b/packages/viewer/.github/workflows/ci-cd.yaml deleted file mode 100644 index c36cce3e..00000000 --- a/packages/viewer/.github/workflows/ci-cd.yaml +++ /dev/null @@ -1,114 +0,0 @@ -name: CI/CD -on: - push: - -jobs: - test: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Cache node modules - uses: actions/cache@v2 - env: - cache-name: cache-node-modules - with: - path: ~/.npm - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-build-${{ env.cache-name }}- - ${{ runner.os }}-build- - ${{ runner.os }}- - - name: Install - run: npm ci - - name: Lint - run: npm run lint:check - - name: Formatting - run: npm run fmt:check - - name: Test - run: npm run test:cov - - name: Report coverage - uses: coverallsapp/github-action@master - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - - build: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Cache node modules - uses: actions/cache@v2 - env: - cache-name: cache-node-modules - with: - path: ~/.npm - key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-build-${{ env.cache-name }}- - ${{ runner.os }}-build- - ${{ runner.os }}- - - name: Install - run: npm ci - - name: Build - run: npm run build - - name: Upload build - uses: actions/upload-artifact@v2 - with: - name: public - path: public - - e2e: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - - name: Cypress run - uses: cypress-io/github-action@v2 - with: - build: npm run build - start: npm run start - record: true - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} - - name: Capture screenshots - if: ${{ failure() }} - uses: actions/upload-artifact@v2 - with: - name: screenshots - path: cypress/screenshots - - name: Capture videos - if: ${{ failure() }} - uses: actions/upload-artifact@v2 - with: - name: videos - path: cypress/videos - - release: - if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/dev' - runs-on: ubuntu-latest - needs: - - build - - test - - e2e - steps: - - name: Download build - uses: actions/download-artifact@v1 - with: - name: public - path: public - - name: Release to dev - if: github.ref == 'refs/heads/dev' - run: | - aws s3 sync ./public s3://pi-base-viewer-dev - env: - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - - name: Release to prod - if: github.ref == 'refs/heads/master' - run: | - aws s3 sync ./public s3://pi-base-viewer - env: - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} diff --git a/packages/viewer/cypress/e2e/deduction.spec.ts b/packages/viewer/cypress/e2e/deduction.spec.ts index ee728c86..7ef44934 100644 --- a/packages/viewer/cypress/e2e/deduction.spec.ts +++ b/packages/viewer/cypress/e2e/deduction.spec.ts @@ -1,6 +1,7 @@ import { isLegacy } from '../constants' beforeEach(() => { + cy.clearAllLocalStorage() cy.intercept({ hostname: /pi-base-bundles/ }, { fixture: 'main.min.json' }) }) diff --git a/packages/viewer/cypress/e2e/search.spec.ts b/packages/viewer/cypress/e2e/search.spec.ts index 836e2b7c..3ab5df62 100644 --- a/packages/viewer/cypress/e2e/search.spec.ts +++ b/packages/viewer/cypress/e2e/search.spec.ts @@ -4,14 +4,15 @@ const search = '/spaces' describe('with a working remote', () => { beforeEach(() => { - cy.intercept({ hostname: /pi-base-bundles/ }, { fixture: 'main.min.json' }) + cy.clearAllLocalStorage() + cy.intercept({ path: /main.json/ }, { fixture: 'main.min.json' }) }) it('searches by text and formula', () => { cy.visit(search) - cy.get('input[name="text"]').type('plank') - cy.get('input[name="q"]').type('metacom') + cy.get('input[name="text"]').focus().type('plank') + cy.get('input[name="q"]').focus().type('metacom') cy.url().should('include', 'text=plank').should('include', 'q=metacom') diff --git a/packages/viewer/cypress/tsconfig.json b/packages/viewer/cypress/tsconfig.json index f0fd98dc..04fc8da8 100644 --- a/packages/viewer/cypress/tsconfig.json +++ b/packages/viewer/cypress/tsconfig.json @@ -1,8 +1,13 @@ { - "extends": "../tsconfig.json", "compilerOptions": { + "target": "es5", + "lib": [ + "es5", + "dom" + ], "types": [ - "cypress" + "cypress", + "node" ], "isolatedModules": false }, diff --git a/packages/viewer/package.json b/packages/viewer/package.json index 46efc868..741bb3b0 100644 --- a/packages/viewer/package.json +++ b/packages/viewer/package.json @@ -9,6 +9,7 @@ "cy:open": "cypress open", "cy:run": "cypress run", "dev": "vite", + "preview": "vite preview", "test": "vitest run", "test:cov": "vitest run --coverage", "test:watch": "vitest", @@ -41,7 +42,7 @@ "@types/katex": "^0.16.5", "@types/page": "^1.11.8", "@types/unist": "^2.0.9", - "cypress": "^12.17.4", + "cypress": "^13.3.3", "svelte": "^3.59.2", "svelte-check": "^3.5.2", "svelte-preprocess": "^5.0.4", diff --git a/packages/viewer/src/constants.ts b/packages/viewer/src/constants.ts index e7fe410f..e46dfeb4 100644 --- a/packages/viewer/src/constants.ts +++ b/packages/viewer/src/constants.ts @@ -1,5 +1,8 @@ +import { bundle } from '@pi-base/core' + export const mainBranch = 'main' export const contributingUrl = `https://github.com/pi-base/data/blob/${mainBranch}/CONTRIBUTING.md` +export const defaultHost = bundle.defaultHost export const build = { branch: import.meta.env.VITE_BRANCH || 'unknown', diff --git a/packages/viewer/src/context.ts b/packages/viewer/src/context.ts index 6137afba..7dfcdc77 100644 --- a/packages/viewer/src/context.ts +++ b/packages/viewer/src/context.ts @@ -39,18 +39,20 @@ function project(store: Store) { export function initialize({ db = local(), errorHandler = Errors.log(), + host, gateway, showDev = false, typesetter = renderer, }: { db?: Local errorHandler?: Errors.Handler + host?: string gateway: Gateway.Sync showDev?: boolean typesetter?: typeof renderer }): Context { const pre = db.load() - const store = create(pre, gateway) + const store = create(pre, gateway, { host }) db.subscribe(project(store)) @@ -79,17 +81,17 @@ export function initialize({ until: Promise = loaded(), ): Promise { return new Promise((resolve, reject) => { - const unsubscribe = s.subscribe(state => { + var unsubscribe = s.subscribe(state => { const found = lookup(state) if (found) { resolve(found) - unsubscribe() + unsubscribe && unsubscribe() } }) until.then(() => { reject() - unsubscribe() + unsubscribe && unsubscribe() }) }) } diff --git a/packages/viewer/src/routes/+layout.ts b/packages/viewer/src/routes/+layout.ts index 8a014a59..7c9319a0 100644 --- a/packages/viewer/src/routes/+layout.ts +++ b/packages/viewer/src/routes/+layout.ts @@ -1,7 +1,10 @@ -import * as errors from '../errors' +import { initialize } from '@/context' +import { defaultHost } from '@/constants' +import * as errors from '@/errors' +import { sync } from '@/gateway' import type { LayoutLoad } from './$types' -import { initialize } from '../context' -import { sync } from '../gateway' + +const bundleHost = import.meta.env.VITE_BUNDLE_HOST || defaultHost export const load: LayoutLoad = async ({ fetch, url: { host } }) => { const dev = host.match(/(dev(elopment)?[.-]|localhost)/) !== null @@ -22,6 +25,7 @@ export const load: LayoutLoad = async ({ fetch, url: { host } }) => { showDev: dev, errorHandler, gateway: sync(fetch), + host: bundleHost, }) await context.loaded() diff --git a/packages/viewer/src/stores/index.ts b/packages/viewer/src/stores/index.ts index f59f62ae..1c5f0344 100644 --- a/packages/viewer/src/stores/index.ts +++ b/packages/viewer/src/stores/index.ts @@ -1,21 +1,22 @@ -export { default as list } from './list' -export { default as search } from './search' - -import { type Readable, type Writable, get, writable } from 'svelte/store' +import { defaultHost, mainBranch } from '@/constants' +import type * as Gateway from '@/gateway' import { Collection, + Theorems, + Traits, type Property, type SerializedTheorem, type Space, - Theorems, type Trait, - Traits, -} from '../models' +} from '@/models' +import { read } from '@/util' +import { get, writable, type Readable, type Writable } from 'svelte/store' import * as Deduction from './deduction' import * as Source from './source' import * as Sync from './sync' -import type * as Gateway from '../gateway' -import { read } from '../util' + +export { default as list } from './list' +export { default as search } from './search' export type Meta = { etag?: string @@ -42,12 +43,25 @@ export type Store = { deduction: Deduction.Store } -export function create(pre: Prestore, gateway: Gateway.Sync): Store { +export function create( + pre: Prestore, + gateway: Gateway.Sync, + { + host = defaultHost, + branch = mainBranch, + }: { + host?: string + branch?: string + } = {}, +): Store { const spaces = writable(Collection.empty()) const properties = writable(Collection.empty()) const theorems = writable(new Theorems()) const traits = writable(new Traits()) - const source = Source.create() + const source = Source.create({ + host, + branch, + }) const sync = Sync.create(refresh, pre.sync) const deduction = Deduction.create( diff --git a/packages/viewer/src/stores/source.ts b/packages/viewer/src/stores/source.ts index 50ef4159..6b4df224 100644 --- a/packages/viewer/src/stores/source.ts +++ b/packages/viewer/src/stores/source.ts @@ -1,7 +1,6 @@ import { type Readable, writable } from 'svelte/store' -import { bundle } from '@pi-base/core' -import { mainBranch } from '../constants' +import { defaultHost, mainBranch } from '../constants' import { trace } from '../debug' import type { Source } from '../types' @@ -14,10 +13,10 @@ export interface Store extends Readable { export const initial: Source = { branch: mainBranch, - host: bundle.defaultHost, + host: defaultHost, } -export function create(source?: Source): Store { +export function create(source: Source): Store { const store = writable(source || initial) const { subscribe, update } = store diff --git a/packages/viewer/vite.config.js b/packages/viewer/vite.config.js index 7b9ea199..d265798f 100644 --- a/packages/viewer/vite.config.js +++ b/packages/viewer/vite.config.js @@ -1,9 +1,32 @@ import { sveltekit } from '@sveltejs/kit/vite' import { defineConfig } from 'vitest/config' +import { readFileSync } from 'node:fs' + +const pathExp = new RegExp('/refs/heads/(.*).json') + +/** @type {import('vite').Plugin} */ +const fixtures = { + name: 'fixtures-middleware', + // See https://kit.svelte.dev/docs/faq#how-do-i-use-x-with-sveltekit-how-do-i-use-middleware + configureServer(server) { + server.middlewares.use((req, res, next) => { + const match = pathExp.exec(req.url) + if (match) { + res.setHeader('Content-Type', 'application/json') + + const ref = match[1] + const data = readFileSync(`./cypress/fixtures/${ref}.min.json`) + return res.end(data) + } + + next() + }) + }, +} // https://vitejs.dev/config/ export default defineConfig({ - plugins: [sveltekit()], + plugins: [sveltekit(), fixtures], test: { include: ['src/**/*.{test,spec}.{js,ts}'], coverage: { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0ec9aec4..3996532a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -221,8 +221,8 @@ importers: specifier: ^2.0.9 version: 2.0.9 cypress: - specifier: ^12.17.4 - version: 12.17.4 + specifier: ^13.3.3 + version: 13.3.3 svelte: specifier: ^3.59.2 version: 3.59.2 @@ -273,8 +273,8 @@ packages: '@jridgewell/trace-mapping': 0.3.9 dev: true - /@cypress/request@2.88.12: - resolution: {integrity: sha512-tOn+0mDZxASFM+cuAP9szGUGPI1HwWVSvdzm7V4cCsPdFTx6qMj29CwaQmRAMIEhORIUBFBsYROYJcveK4uOjA==} + /@cypress/request@3.0.1: + resolution: {integrity: sha512-TWivJlJi8ZDx2wGOw1dbLuHJKUYX7bWySw377nlnGOW3hP9/MUKIsEdXT/YngWxVdgNCHRBmFlBipE+5/2ZZlQ==} engines: {node: '>= 6'} dependencies: aws-sign2: 0.7.0 @@ -1218,14 +1218,16 @@ packages: /@types/ms@0.7.31: resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==} - /@types/node@16.18.59: - resolution: {integrity: sha512-PJ1w2cNeKUEdey4LiPra0ZuxZFOGvetswE8qHRriV/sUkL5Al4tTmPV9D2+Y/TPIxTHHgxTfRjZVKWhPw/ORhQ==} - dev: true - /@types/node@18.16.6: resolution: {integrity: sha512-N7KINmeB8IN3vRR8dhgHEp+YpWvGFcpDoh5XZ8jB5a00AdFKCKEyyGTOPTddUf4JqU1ZKTVxkOxakDvchNVI2Q==} dev: true + /@types/node@18.18.7: + resolution: {integrity: sha512-bw+lEsxis6eqJYW8Ql6+yTqkE6RuFtsQPSe5JxXbqYRFQEER5aJA9a5UH9igqDWm3X4iLHIKOHlnAXLM4mi7uQ==} + dependencies: + undici-types: 5.26.5 + dev: true + /@types/node@20.1.1: resolution: {integrity: sha512-uKBEevTNb+l6/aCQaKVnUModfEMjAl98lw2Si9P5y4hLu9tm6AlX2ZIoXZX6Wh9lJueYPrGPKk5WMCNHg/u6/A==} dev: true @@ -1288,7 +1290,7 @@ packages: resolution: {integrity: sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==} requiresBuild: true dependencies: - '@types/node': 18.16.6 + '@types/node': 18.18.7 dev: true optional: true @@ -2005,15 +2007,15 @@ packages: type-fest: 1.4.0 dev: true - /cypress@12.17.4: - resolution: {integrity: sha512-gAN8Pmns9MA5eCDFSDJXWKUpaL3IDd89N9TtIupjYnzLSmlpVr+ZR+vb4U/qaMp+lB6tBvAmt7504c3Z4RU5KQ==} - engines: {node: ^14.0.0 || ^16.0.0 || >=18.0.0} + /cypress@13.3.3: + resolution: {integrity: sha512-mbdkojHhKB1xbrj7CrKWHi22uFx9P9vQFiR0sYDZZoK99OMp9/ZYN55TO5pjbXmV7xvCJ4JwBoADXjOJK8aCJw==} + engines: {node: ^16.0.0 || ^18.0.0 || >=20.0.0} hasBin: true requiresBuild: true dependencies: - '@cypress/request': 2.88.12 + '@cypress/request': 3.0.1 '@cypress/xvfb': 1.2.4(supports-color@8.1.1) - '@types/node': 16.18.59 + '@types/node': 18.18.7 '@types/sinonjs__fake-timers': 8.1.1 '@types/sizzle': 2.3.3 arch: 2.2.0 @@ -5864,6 +5866,10 @@ packages: resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==} dev: true + /undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + dev: true + /undici@5.26.5: resolution: {integrity: sha512-cSb4bPFd5qgR7qr2jYAi0hlX9n5YKK2ONKkLFkxl+v/9BvC0sOpZjBHDBSXc5lWAf5ty9oZdRXytBIHzgUcerw==} engines: {node: '>=14.0'}