@commonpub/layer 0.71.1 → 0.71.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@commonpub/layer",
3
- "version": "0.71.1",
3
+ "version": "0.71.2",
4
4
  "type": "module",
5
5
  "main": "./nuxt.config.ts",
6
6
  "files": [
@@ -54,14 +54,14 @@
54
54
  "vue-router": "^4.3.0",
55
55
  "zod": "^4.3.6",
56
56
  "@commonpub/auth": "0.8.0",
57
+ "@commonpub/config": "0.21.0",
58
+ "@commonpub/docs": "0.6.3",
57
59
  "@commonpub/editor": "0.7.11",
58
- "@commonpub/learning": "0.5.2",
59
60
  "@commonpub/explainer": "0.7.15",
61
+ "@commonpub/learning": "0.5.2",
60
62
  "@commonpub/protocol": "0.13.0",
61
- "@commonpub/config": "0.21.0",
62
63
  "@commonpub/schema": "0.39.0",
63
64
  "@commonpub/server": "2.84.1",
64
- "@commonpub/docs": "0.6.3",
65
65
  "@commonpub/theme-studio": "0.5.1",
66
66
  "@commonpub/ui": "0.12.2"
67
67
  },
@@ -1,150 +0,0 @@
1
- /**
2
- * Component tests for the contest entry-detail page (artifact timeline).
3
- *
4
- * Locks: the content summary card, the stage-ordered artifact timeline with
5
- * template-labelled fields, url fields rendered as safe links, orphaned values
6
- * (template field later removed) still rendering, artifact section hidden when
7
- * the server stripped artifacts (unprivileged viewer), and an axe scan.
8
- *
9
- * Page uses Nuxt auto-imports (useRoute, useLazyFetch, useSeoMeta, useSiteName,
10
- * plus the auto-imported contestStages utils) — stub them on globalThis.
11
- */
12
- import { describe, it, expect, vi, beforeEach } from 'vitest';
13
- import { render } from '@testing-library/vue';
14
- import { defineComponent, h, ref } from 'vue';
15
- import axe from 'axe-core';
16
- import EntryDetailPage from '../[entryId].vue';
17
- import { normalizeStages, currentStageId } from '../../../../../utils/contestStages';
18
-
19
- const NuxtLink = defineComponent({
20
- name: 'NuxtLink',
21
- props: { to: String },
22
- setup(props, { slots }) {
23
- return () => h('a', { href: props.to }, slots.default?.());
24
- },
25
- });
26
- const stubs = { NuxtLink };
27
-
28
- const STAGES = [
29
- { id: 'prop', name: 'Proposals', kind: 'submission', submissionTemplate: [
30
- { key: 'summary', label: 'Summary', type: 'textarea', required: true },
31
- ] },
32
- { id: 'rev1', name: 'Screening', kind: 'review' },
33
- { id: 'proto', name: 'Prototype', kind: 'submission', submissionTemplate: [
34
- { key: 'repo_url', label: 'Repository URL', type: 'url', required: true },
35
- ] },
36
- ];
37
-
38
- function makeContest(overrides: Record<string, unknown> = {}) {
39
- return {
40
- title: 'Resilient America',
41
- status: 'active',
42
- startDate: '2026-04-01T00:00:00.000Z',
43
- endDate: '2026-08-01T00:00:00.000Z',
44
- judgingEndDate: null,
45
- stages: STAGES,
46
- currentStageId: 'proto',
47
- ...overrides,
48
- };
49
- }
50
-
51
- function makeEntry(overrides: Record<string, unknown> = {}) {
52
- return {
53
- id: 'e1',
54
- contestId: 'c1',
55
- contentId: 'ct1',
56
- userId: 'u1',
57
- score: 88,
58
- rank: 2,
59
- stageState: [{ stageId: 'rev1', status: 'advanced' }],
60
- eliminated: false,
61
- stageSubmissions: [
62
- // Deliberately out of stage order — the timeline must sort by stage.
63
- { stageId: 'proto', fields: { repo_url: 'https://github.com/x/y' }, submittedAt: '2026-07-01T12:00:00.000Z' },
64
- { stageId: 'prop', fields: { summary: 'A mesh network.', legacy_field: 'kept' }, submittedAt: '2026-05-01T12:00:00.000Z' },
65
- ],
66
- submittedAt: '2026-04-20T12:00:00.000Z',
67
- contentTitle: 'Solar Mesh Node',
68
- contentSlug: 'solar-mesh-node',
69
- contentType: 'project',
70
- contentCoverImageUrl: null,
71
- authorName: 'Ada Maker',
72
- authorUsername: 'ada',
73
- authorAvatarUrl: null,
74
- };
75
- }
76
-
77
- let contestData: Record<string, unknown> | null = makeContest();
78
- let entryData: Record<string, unknown> | null = makeEntry();
79
-
80
- Object.assign(globalThis, {
81
- useRoute: () => ({ params: { slug: 'resilient', entryId: 'e1' } }),
82
- useLazyFetch: vi.fn((url: string) => ({
83
- data: ref(String(url).includes('/entries/') ? entryData : contestData),
84
- error: ref(null),
85
- })),
86
- useSeoMeta: () => {},
87
- useSiteName: () => 'Test',
88
- normalizeStages,
89
- currentStageId,
90
- });
91
-
92
- function mount() {
93
- return render(EntryDetailPage, { global: { stubs } });
94
- }
95
-
96
- beforeEach(() => {
97
- contestData = makeContest();
98
- entryData = makeEntry();
99
- });
100
-
101
- describe('entry detail page', () => {
102
- it('shows the content summary with author, status badges, and a project link', () => {
103
- const { container } = mount();
104
- expect(container.querySelector('.cpub-ed-title')?.textContent).toBe('Solar Mesh Node');
105
- expect(container.textContent).toContain('Ada Maker');
106
- expect(container.textContent).toContain('Advanced');
107
- expect(container.textContent).toContain('#2');
108
- expect(container.textContent).toContain('Score 88');
109
- const projectLink = Array.from(container.querySelectorAll('a')).find((a) => a.textContent?.includes('View the project'));
110
- expect(projectLink?.getAttribute('href')).toBe('/u/ada/project/solar-mesh-node');
111
- });
112
-
113
- it('renders the artifact timeline in stage order with template labels', () => {
114
- const { container } = mount();
115
- const names = Array.from(container.querySelectorAll('.cpub-ed-stagename')).map((n) => n.textContent);
116
- expect(names).toEqual(['Proposals', 'Prototype']); // stage order, not submit order
117
- expect(container.textContent).toContain('Summary');
118
- expect(container.textContent).toContain('A mesh network.');
119
- });
120
-
121
- it('renders url fields as hardened external links', () => {
122
- const { container } = mount();
123
- const link = Array.from(container.querySelectorAll('.cpub-ed-fields a')).find((a) => a.textContent === 'https://github.com/x/y');
124
- expect(link).toBeTruthy();
125
- expect(link!.getAttribute('rel')).toContain('noopener');
126
- });
127
-
128
- it('still renders values whose template field was later removed (never drop data)', () => {
129
- const { container } = mount();
130
- expect(container.textContent).toContain('legacy_field');
131
- expect(container.textContent).toContain('kept');
132
- });
133
-
134
- it('hides the artifact section entirely when the server stripped artifacts', () => {
135
- // The route handler `delete`s the key for unprivileged viewers — mirror that.
136
- const stripped = makeEntry();
137
- delete (stripped as Record<string, unknown>).stageSubmissions;
138
- entryData = stripped;
139
- const { container } = mount();
140
- expect(container.querySelector('.cpub-ed-stages')).toBeNull();
141
- // The content card still shows — the page is useful to the public.
142
- expect(container.querySelector('.cpub-ed-title')?.textContent).toBe('Solar Mesh Node');
143
- });
144
-
145
- it('passes an axe scan', async () => {
146
- const { container } = mount();
147
- const results = await axe.run(container);
148
- expect(results.violations).toEqual([]);
149
- });
150
- });