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
39 changes: 28 additions & 11 deletions doc/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,25 +70,42 @@ against:
exercise the actual deployment end-to-end and confirm the sites stay
consistent.

| `CYPRESS_ENV` | URL | Mode |
| -------------------- | ------------------------------------ | ------- |
| `local` (default) | `http://localhost:5173` | fixture |
| `pages` | `https://topology.pi-base.org` | live |
| `workers` | `topology.pi-base.workers.dev` | live |
| `graphs` | `graphs.pi-base.workers.dev` | live |
| `preview` | `$PREVIEW_URL` | live |

The whole suite runs in both modes; assertions are written to hold against the
| `CYPRESS_ENV` | URL | Mode |
| ----------------- | ------------------------------ | ------- |
| `local` (default) | `http://localhost:5173` | fixture |
| `pages` | `https://topology.pi-base.org` | live |
| `workers` | `topology.pi-base.workers.dev` | live |
| `graphs` | `graphs.pi-base.workers.dev` | live |
| `preview` | `$PREVIEW_URL` | live |

The suite runs in both modes; assertions are written to hold against the
real data (stable IDs, math-free name prefixes, behavioral checks) rather than
exact HTML snapshots. Set `CYPRESS_MODE=fixture` to force the deterministic
fixture against a deployed URL while debugging.
exact HTML snapshots. Specs that can only hold against fixture-pinned content
(e.g. the space-to-space navigation regression, which follows a description
link that only exists in the fixture) are declared with a `fixtureIt` helper
and show as pending in live runs. Set `CYPRESS_MODE=fixture` to force the
deterministic fixture against a deployed URL while debugging.

The fixture (`cypress/fixtures/main.min.json`) is a hand-curated subset; keep its
tested entities (e.g. `S000001`, `S000004`, `P000001`) in sync with live data.
Note it predates a pi-base property-ID reorganization, so its property `uid`s are
**not** interchangeable with the current bundle's — refresh display fields per
entity, don't bulk-remap by `uid`.

Two things to know when running fixture mode against a production-style build
(`VITE_BUNDLE_HOST=http://localhost:4173 pnpm run build` + `pnpm run preview`):

- The bundle is baked in at build time: with a localhost `VITE_BUNDLE_HOST`,
`+layout.server.ts` imports `public/refs/heads/main.json` (a symlink to the
Cypress fixture) and injects it during SSR, so the browser never fetches
`main.json`. The `cy.intercept` in `cypress/support/commands.ts` is inert in
this mode (it matters against `pnpm dev` and deployed targets, where the
client fetches the bundle). To vary fixture data here, edit the fixture and
rebuild — intercepting won't work.
- Restart the preview server after every rebuild. A still-running server can
serve HTML referencing the previous build's hashed chunks, which fails with
"Failed to fetch dynamically imported module".

## Remote End-to-End Testing

The `./bin/e2e` script runs the suite against the deployed targets (live data),
Expand Down
32 changes: 32 additions & 0 deletions packages/viewer/cypress/e2e/space.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import { deduce, setup } from '../support'

beforeEach(setup)

// Marks specs that depend on exact fixture-pinned content and only run in
// fixture mode (see doc/testing.md).
const fixtureIt = Cypress.env('mode') === 'live' ? it.skip : it

function clickTraitFor(name: string) {
cy.get('.related-traits')
.contains(name)
Expand Down Expand Up @@ -51,3 +55,31 @@ it('displays references', () => {

cy.get('ul.references').should('exist')
})

// Navigating between space pages reuses the mounted component, and the traits
// table must recompute for the new space instead of continuing to show the
// previous space's data. See https://github.com/pi-base/web/issues/255.
//
// Fixture-only: relies on S000001's fixture description linking to {S4} (live
// descriptions differ) and on "Indiscrete" being asserted false for S000001
// and true for S000004, so the value icon flips iff the table recomputes.
fixtureIt('recomputes traits when navigating between spaces', () => {
function indiscreteRow() {
return cy.get('.related-traits').contains('tr', 'Indiscrete')
}

cy.visit('spaces/S000001')

cy.contains('h1', 'Discrete topology on')
indiscreteRow().find('.bi-x').should('exist')

// Client-side navigation via the in-description link (note the unpadded id)
cy.get('.description').contains('a', 'Indiscrete topology on').click()
cy.location('pathname').should('eq', '/spaces/S4')
cy.contains('h1', 'Indiscrete topology on')
indiscreteRow().find('.bi-check').should('exist')

cy.go('back')
cy.location('pathname').should('eq', '/spaces/S000001')
indiscreteRow().find('.bi-x').should('exist')
})
2 changes: 1 addition & 1 deletion packages/viewer/cypress/fixtures/main.min.json
Original file line number Diff line number Diff line change
Expand Up @@ -945,7 +945,7 @@
"uid": "S000001",
"counterexamples_id": 1,
"name": "Discrete topology on $\\{0,1\\}$",
"description": "-",
"description": "Compare with the indiscrete topology {S4} on the same set.",
"aliases": ["Discrete topology on a two-point set", "Finite discrete topology"],
"refs": []
},
Expand Down
8 changes: 2 additions & 6 deletions packages/viewer/src/components/Properties/Spaces.svelte
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
<script lang="ts">
import { Link } from '../Shared'
import { Related } from '../Traits'
import type { Property, Space, Trait, Traits } from '@/models'
import type { Property } from '@/models'

export let property: Property

function related(traits: Traits): [Space, Property, Trait | undefined][] {
return traits.forPropertyAll(property).map(([s, t]) => [s, property, t])
}
</script>

<Related mode="spaces" {related}>
<Related anchor={{ mode: 'spaces', property }}>
<Link.Space slot="id" let:space {space} content="id" />

<Link.Space slot="name" let:space {space} />
Expand Down
8 changes: 2 additions & 6 deletions packages/viewer/src/components/Spaces/Properties.svelte
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
<script lang="ts">
import { Link } from '../Shared'
import { Related } from '../Traits'
import type { Property, Space, Trait, Traits } from '@/models'
import type { Space } from '@/models'

export let space: Space

function related(traits: Traits): [Space, Property, Trait | undefined][] {
return traits.forSpaceAll(space).map(([p, t]) => [space, p, t])
}
</script>

<Related mode="properties" {related}>
<Related anchor={{ mode: 'properties', space }}>
<Link.Property slot="id" let:property {property} content="id" />

<Link.Property slot="name" let:property {property} />
Expand Down
28 changes: 23 additions & 5 deletions packages/viewer/src/components/Traits/Related.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,28 @@
import urlSearchParam from '@/stores/urlSearchParam'
import { checkIfRedundant } from '@/stores/deduction'

export let related: (traits: Traits) => [Space, Property, Trait | undefined][]
export let mode: 'spaces' | 'properties'
type Row = [Space, Property, Trait | undefined]

// The entity whose related traits are listed. Passing the entity itself
// (rather than a closure over it) keeps the data dependency visible to
// Svelte's reactivity, so the table recomputes when navigating between
// entities. See https://github.com/pi-base/web/issues/255.
export let anchor:
| { mode: 'properties'; space: Space }
| { mode: 'spaces'; property: Property }

const { theorems, traits } = context()

function rows(a: typeof anchor, traits: Traits): Row[] {
if (a.mode === 'properties') {
const { space } = a
return traits.forSpaceAll(space).map(([p, t]) => [space, p, t])
} else {
const { property } = a
return traits.forPropertyAll(property).map(([s, t]) => [s, property, t])
}
}

const filter = writable('')
urlSearchParam('filter', filter)

Expand All @@ -39,11 +56,12 @@
}
}

$: all = related($traits)
// all has type [Space, Property, Trait][]
$: all = rows(anchor, $traits)
// we need to index names in different positions depending on which kind we
// are displaying
$: index = new Fuse(all, { keys: [`${mode === 'spaces' ? 0 : 1}.name`] })
$: index = new Fuse(all, {
keys: [`${anchor.mode === 'spaces' ? 0 : 1}.name`],
})
$: searched = $filter ? index.search($filter).map(r => r.item) : all
$: filtered = searched.filter(([_space, _property, t]) =>
matchesFilter(filterMode, t),
Expand Down
3 changes: 1 addition & 2 deletions packages/viewer/src/routes/(app)/spaces/[id]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@
import { page } from '$app/stores'

export let data: PageData
let rel = $page.url.pathname

$: title = `S${data.space.id}: ${data.space.name}`
</script>

<Title {title} />

<Show space={data.space} tab="properties" {rel} />
<Show space={data.space} tab="properties" rel={$page.url.pathname} />
Loading