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
12 changes: 12 additions & 0 deletions examples/app-showcase/src/ui/pages/task-visualizations.pages.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -96,6 +96,18 @@ export const TaskMapPage = definePage({
label: 'Work Map',
interfaceConfig: {
source: 'showcase_task',
// `InterfacePageConfigSchema` is a CLOSED shape with no `map` (or
// `kanban`/`calendar`/…) key of its own — an author cannot declare a
// marker-title binding directly here (confirmed: `map: {...}` on this
// object is rejected as an unrecognized key). The one schema-legal
// channel for a per-visualization field binding on an interface page is
// `sourceView`, which `InterfaceListPage`'s `resolveSourceView` resolves
// against the source object's OWN named view — and `showcase_task`
// already declares exactly this binding on its `map` listView
// (task.view.ts, `listViews.map.map: { titleField: 'title',
// locationField: 'location' }`, from #9340). Referencing it here is a
// pure forward, not a duplicate declaration.
sourceView: 'map',
columns: [...cols, 'location'],
appearance: { showDescription: true, allowedVisualizations: ['map'] },
userActions: { sort: false, search: true, filter: false, rowHeight: false, addRecordForm: false },
Expand Down
60 changes: 60 additions & 0 deletions examples/app-showcase/test/task-map-marker-title.test.ts
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.

import { describe, it, expect } from 'vitest';

import { PageSchema } from '@objectstack/spec/ui';

import { TaskMapPage } from '../src/ui/pages/task-visualizations.pages.js';
import { TaskViews } from '../src/ui/views/task.view.js';

/**
* Regression pin for the showcase Work Map's placeholder marker titles.
*
* `InterfacePageConfigSchema` is a CLOSED shape with no `map` (or `kanban` /
* `calendar` / …) key of its own — a per-visualization field binding cannot
* be declared directly on `interfaceConfig`. The one schema-legal channel is
* `sourceView`, which objectui's `InterfaceListPage` resolves against the
* source object's OWN named view and (since objectui#5908) forwards that
* view's `map` block verbatim to the renderer. `showcase_task` already
* declares the correct binding on its `map` listView (task.view.ts, #9340);
* this page has to REFERENCE it, or the renderer's `titleField || 'name'`
* fallback finds no `name` field on `showcase_task` and every marker title
* renders as a placeholder.
*/
describe('showcase Work Map — marker title binding (#11443)', () => {
it('references the object\'s `map` listView via sourceView', () => {
expect(TaskMapPage.interfaceConfig?.sourceView).toBe('map');
});

it('the referenced `map` listView still carries the title/location binding', () => {
const mapView = (TaskViews.listViews as Record<string, any>).map;
expect(mapView).toBeDefined();
expect(mapView.type).toBe('map');
expect(mapView.map).toEqual({ titleField: 'title', locationField: 'location' });
});

it('a `map` block declared directly on interfaceConfig is rejected — documents the schema boundary this page works around', () => {
const attempt = () =>
PageSchema.parse({
type: 'list',
object: 'showcase_task',
kind: 'full',
template: 'default',
isDefault: false,
regions: [],
name: 'showcase_task_map_direct_probe',
label: 'Work Map (direct-block probe)',
interfaceConfig: {
source: 'showcase_task',
columns: ['title', 'location'],
appearance: { showDescription: true, allowedVisualizations: ['map'] },
// `map` is not a key `InterfacePageConfigSchema` declares — the
// input TS type does not close over it (no excess-property error),
// but `.parse()` rejects it at runtime as an unrecognized key. That
// runtime rejection is exactly what this test pins.
map: { titleField: 'title', locationField: 'location' },
},
});
expect(attempt).toThrow(/[Uu]nrecognized key/);
});
});
Loading