fixtures.client.ts 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869
  1. /**
  2. * Shared spec fixtures. The kit has no notion of a guide or a preview tab, so
  3. * the suites supply their own content families — exactly as an embedder does.
  4. */
  5. import type { NodeId, PaneId, PaneNode, SplitId, SplitNode, TabId, TabRecord } from '../src/contract/types.ts'
  6. import { createIdMinter, createInitialState, DockController, type IdMinter } from '../src/index.ts'
  7. import type { DockLabels } from '../src/contract/adapter.ts'
  8. import type { LayoutState } from '../src/contract/types.ts'
  9. /** Consistency id of the seeded tab these suites use. */
  10. export const SEED_CONTENT_ID = 'seed:start'
  11. /** Title of the seeded tab. */
  12. export const SEED_TITLE = 'Start'
  13. /** The seeded tab an embedder would put in a fresh pane. */
  14. export function seedTab(id: TabId): TabRecord {
  15. return { id, kind: 'seed', contentId: SEED_CONTENT_ID, title: SEED_TITLE }
  16. }
  17. /** A content tab addressed by a file URL, the shape the first embedder uses. */
  18. export function fileTab(id: TabId, contentId: string, title: string): TabRecord {
  19. return { id, kind: 'file', contentId, title }
  20. }
  21. /** A controller seeded on both the initial pane and every pane a split creates. */
  22. export function seededController(): DockController {
  23. return new DockController({ makeInitialTab: seedTab, makePaneTab: seedTab })
  24. }
  25. /** An initial state holding one seeded tab, plus the minter that produced it. */
  26. export function seededState(): { state: LayoutState; minter: IdMinter } {
  27. const minter = createIdMinter()
  28. return { state: createInitialState(minter, seedTab), minter }
  29. }
  30. /** Labels a spec can pass without caring what they say. */
  31. export const TEST_LABELS: DockLabels = {
  32. emptyPane: 'empty pane',
  33. splitPane: 'split right',
  34. splitPaneDisabled: 'pane budget spent',
  35. splitPaneNarrow: 'too narrow to split',
  36. closeTab: 'close',
  37. addTab: 'new tab',
  38. dockFloat: 'dock',
  39. closeFloat: 'close panel',
  40. dropZone: { center: 'move here', left: 'split left', right: 'split right', top: 'split top', bottom: 'split bottom' },
  41. }
  42. /** Brand a literal a spec spells out: an id the kit would have minted. */
  43. export const asPane = (id: string): PaneId => id as PaneId
  44. /** See {@link asPane}. */
  45. export const asSplit = (id: string): SplitId => id as SplitId
  46. /** See {@link asPane}. */
  47. export const asTab = (id: string): TabId => id as TabId
  48. /** The first tab of a pane the spec knows is not empty. */
  49. export function firstTab(pane: PaneNode): TabId {
  50. const id = pane.tabs[0]
  51. if (id === undefined) throw new Error(`fixture: pane ${pane.id} holds no tab`)
  52. return id
  53. }
  54. /** A split's child at an index the spec knows exists. */
  55. export function childAt(split: SplitNode, index: number): NodeId {
  56. const id = split.children[index]
  57. if (id === undefined) throw new Error(`fixture: split ${split.id} has no child ${String(index)}`)
  58. return id
  59. }