Files
supabase/apps/studio/state/notebooks/notebooks-state.test.ts
Charis 0111aa371b ref(studio): converge notebook UI types with canonical content schema (#48905)
## Summary
- Joshen's `state/notebooks/types.ts` (Explorer/notebook editor UI)
redefined its own `TimeRange`, cell union, and `NotebookContent` shapes,
duplicating the canonical schema from
`data/content/notebooks/notebook-schema.ts` (#48813, #48815).
- Points `Notebook.content` and `notebooksState.updateCells` at the
canonical `Notebooks.Content` / `Notebooks.Cell` types (via `@/types`)
instead, and fixes the handful of call sites that constructed notebook
content by hand to match the real wire shape: `schema_version: 1` (not
`'1.0'`) and `_tag`-discriminated cells (e.g. `{ _tag: 'markdown_cell',
id, text }` instead of `{ type: 'markdown', content }`).
- No behavioral changes — Joshen's state management, editor component,
and hooks are untouched aside from the type-level fixes needed to
compile against the canonical schema.

## Test plan
- [x] `pnpm exec tsc --noEmit` — no new errors
- [x] `pnpm exec vitest run state/notebooks/notebooks-state.test.ts
components/interfaces/Explorer/__tests__/NotebookEditor.test.tsx` — 8/8
passing
- [x] `pnpm exec eslint` on changed files — clean
- [x] `pnpm exec prettier --check` on changed files — clean

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Updated notebook content handling to use the current schema version
format.
* Improved compatibility for markdown cells, including their identifiers
and text.
* Standardized notebook content and cell updates for more consistent
behavior.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-10 13:39:02 -04:00

80 lines
2.8 KiB
TypeScript

import { beforeEach, describe, expect, it } from 'vitest'
import { notebooksState } from './notebooks-state'
import type { Notebook } from './types'
function makeNotebook(id: string, overrides: Partial<Notebook> = {}): Notebook {
return {
id,
type: 'notebook',
name: 'My Notebook',
description: '',
visibility: 'project',
favorite: false,
owner_id: 7,
project_id: 42,
content: { schema_version: 1, cells: [] },
...overrides,
}
}
describe('notebooksState', () => {
beforeEach(() => {
// notebooksState is a module-level singleton, so reset the state these tests touch
for (const id of Object.keys(notebooksState.notebooks)) {
delete notebooksState.notebooks[id]
}
notebooksState.needsSaving.clear()
})
it('addNotebook marks a locally-created notebook as new', () => {
notebooksState.addNotebook({ projectRef: 'ref', notebook: makeNotebook('notebook-1') })
expect(notebooksState.notebooks['notebook-1'].status).toBe('new')
})
it('setNotebook marks a notebook not yet in the store as saved', () => {
notebooksState.setNotebook({ projectRef: 'ref', notebook: makeNotebook('notebook-1') })
expect(notebooksState.notebooks['notebook-1'].status).toBe('saved')
})
it('editing a loaded (saved) notebook transitions it to unsaved and queues it for saving', () => {
notebooksState.setNotebook({ projectRef: 'ref', notebook: makeNotebook('notebook-1') })
notebooksState.updateCells({
id: 'notebook-1',
cells: [{ _tag: 'markdown_cell', id: 'cell-1', text: 'hello' }],
})
expect(notebooksState.notebooks['notebook-1'].status).toBe('unsaved')
expect(notebooksState.needsSaving.get('notebook-1')).toBe(false)
})
it('editing a notebook that has never been saved keeps it as new', () => {
notebooksState.addNotebook({ projectRef: 'ref', notebook: makeNotebook('notebook-1') })
notebooksState.updateCells({
id: 'notebook-1',
cells: [{ _tag: 'markdown_cell', id: 'cell-1', text: 'hello' }],
})
expect(notebooksState.notebooks['notebook-1'].status).toBe('new')
})
it('setNotebook does not downgrade an already-loaded notebook back to saved after edits', () => {
notebooksState.setNotebook({ projectRef: 'ref', notebook: makeNotebook('notebook-1') })
notebooksState.updateCells({
id: 'notebook-1',
cells: [{ _tag: 'markdown_cell', id: 'cell-1', text: 'hello' }],
})
expect(notebooksState.notebooks['notebook-1'].status).toBe('unsaved')
// Re-fetching/merging content for the same notebook (e.g. a second setNotebook
// call) must not reset its status back to 'saved' while edits are pending.
notebooksState.setNotebook({ projectRef: 'ref', notebook: makeNotebook('notebook-1') })
expect(notebooksState.notebooks['notebook-1'].status).toBe('unsaved')
})
})