@teleporthq/teleport-project-generator-next 0.43.13 → 0.43.14
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/__tests__/analytics-project-plugin.test.ts +132 -0
- package/__tests__/calendarkit-end2end.test.ts +98 -0
- package/__tests__/calendarkit-project-plugin.test.ts +99 -0
- package/__tests__/dragdrop-kanban-end2end.test.ts +176 -0
- package/__tests__/dragdrop-kanban-project-plugins.test.ts +144 -0
- package/__tests__/global-state-default-value.test.ts +69 -0
- package/dist/cjs/analytics/project-plugin.d.ts +8 -0
- package/dist/cjs/analytics/project-plugin.d.ts.map +1 -0
- package/dist/cjs/analytics/project-plugin.js +178 -0
- package/dist/cjs/analytics/project-plugin.js.map +1 -0
- package/dist/cjs/analytics/tracker-component.d.ts +2 -0
- package/dist/cjs/analytics/tracker-component.d.ts.map +1 -0
- package/dist/cjs/analytics/tracker-component.js +8 -0
- package/dist/cjs/analytics/tracker-component.js.map +1 -0
- package/dist/cjs/analytics/tracker-source.d.ts +2 -0
- package/dist/cjs/analytics/tracker-source.d.ts.map +1 -0
- package/dist/cjs/analytics/tracker-source.js +15 -0
- package/dist/cjs/analytics/tracker-source.js.map +1 -0
- package/dist/cjs/app-import-injection.d.ts +11 -0
- package/dist/cjs/app-import-injection.d.ts.map +1 -0
- package/dist/cjs/app-import-injection.js +42 -0
- package/dist/cjs/app-import-injection.js.map +1 -0
- package/dist/cjs/calendar/calendarkit-css.d.ts +3 -0
- package/dist/cjs/calendar/calendarkit-css.d.ts.map +1 -0
- package/dist/cjs/calendar/calendarkit-css.js +9 -0
- package/dist/cjs/calendar/calendarkit-css.js.map +1 -0
- package/dist/cjs/calendar/project-plugin.d.ts +16 -0
- package/dist/cjs/calendar/project-plugin.d.ts.map +1 -0
- package/dist/cjs/calendar/project-plugin.js +93 -0
- package/dist/cjs/calendar/project-plugin.js.map +1 -0
- package/dist/cjs/drag-drop/component-generator.d.ts +21 -0
- package/dist/cjs/drag-drop/component-generator.d.ts.map +1 -0
- package/dist/cjs/drag-drop/component-generator.js +27 -0
- package/dist/cjs/drag-drop/component-generator.js.map +1 -0
- package/dist/cjs/drag-drop/project-plugin.d.ts +15 -0
- package/dist/cjs/drag-drop/project-plugin.d.ts.map +1 -0
- package/dist/cjs/drag-drop/project-plugin.js +96 -0
- package/dist/cjs/drag-drop/project-plugin.js.map +1 -0
- package/dist/cjs/global-state/project-plugin.d.ts.map +1 -1
- package/dist/cjs/global-state/project-plugin.js +16 -6
- package/dist/cjs/global-state/project-plugin.js.map +1 -1
- package/dist/cjs/index.d.ts +6 -0
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +50 -13
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/kanban/component-generator.d.ts +15 -0
- package/dist/cjs/kanban/component-generator.d.ts.map +1 -0
- package/dist/cjs/kanban/component-generator.js +21 -0
- package/dist/cjs/kanban/component-generator.js.map +1 -0
- package/dist/cjs/kanban/project-plugin.d.ts +13 -0
- package/dist/cjs/kanban/project-plugin.d.ts.map +1 -0
- package/dist/cjs/kanban/project-plugin.js +105 -0
- package/dist/cjs/kanban/project-plugin.js.map +1 -0
- package/dist/cjs/local-component-path-plugin.d.ts +28 -0
- package/dist/cjs/local-component-path-plugin.d.ts.map +1 -0
- package/dist/cjs/local-component-path-plugin.js +93 -0
- package/dist/cjs/local-component-path-plugin.js.map +1 -0
- package/dist/cjs/next-project-mapping.d.ts.map +1 -1
- package/dist/cjs/next-project-mapping.js +57 -0
- package/dist/cjs/next-project-mapping.js.map +1 -1
- package/dist/cjs/rich-text-editor/project-plugin.d.ts.map +1 -1
- package/dist/cjs/rich-text-editor/project-plugin.js +2 -106
- package/dist/cjs/rich-text-editor/project-plugin.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/cjs/uidl-element-traversal.d.ts +18 -0
- package/dist/cjs/uidl-element-traversal.d.ts.map +1 -0
- package/dist/cjs/uidl-element-traversal.js +131 -0
- package/dist/cjs/uidl-element-traversal.js.map +1 -0
- package/dist/esm/analytics/project-plugin.d.ts +8 -0
- package/dist/esm/analytics/project-plugin.d.ts.map +1 -0
- package/dist/esm/analytics/project-plugin.js +175 -0
- package/dist/esm/analytics/project-plugin.js.map +1 -0
- package/dist/esm/analytics/tracker-component.d.ts +2 -0
- package/dist/esm/analytics/tracker-component.d.ts.map +1 -0
- package/dist/esm/analytics/tracker-component.js +5 -0
- package/dist/esm/analytics/tracker-component.js.map +1 -0
- package/dist/esm/analytics/tracker-source.d.ts +2 -0
- package/dist/esm/analytics/tracker-source.d.ts.map +1 -0
- package/dist/esm/analytics/tracker-source.js +12 -0
- package/dist/esm/analytics/tracker-source.js.map +1 -0
- package/dist/esm/app-import-injection.d.ts +11 -0
- package/dist/esm/app-import-injection.d.ts.map +1 -0
- package/dist/esm/app-import-injection.js +38 -0
- package/dist/esm/app-import-injection.js.map +1 -0
- package/dist/esm/calendar/calendarkit-css.d.ts +3 -0
- package/dist/esm/calendar/calendarkit-css.d.ts.map +1 -0
- package/dist/esm/calendar/calendarkit-css.js +6 -0
- package/dist/esm/calendar/calendarkit-css.js.map +1 -0
- package/dist/esm/calendar/project-plugin.d.ts +16 -0
- package/dist/esm/calendar/project-plugin.d.ts.map +1 -0
- package/dist/esm/calendar/project-plugin.js +90 -0
- package/dist/esm/calendar/project-plugin.js.map +1 -0
- package/dist/esm/drag-drop/component-generator.d.ts +21 -0
- package/dist/esm/drag-drop/component-generator.d.ts.map +1 -0
- package/dist/esm/drag-drop/component-generator.js +23 -0
- package/dist/esm/drag-drop/component-generator.js.map +1 -0
- package/dist/esm/drag-drop/project-plugin.d.ts +15 -0
- package/dist/esm/drag-drop/project-plugin.d.ts.map +1 -0
- package/dist/esm/drag-drop/project-plugin.js +93 -0
- package/dist/esm/drag-drop/project-plugin.js.map +1 -0
- package/dist/esm/global-state/project-plugin.d.ts.map +1 -1
- package/dist/esm/global-state/project-plugin.js +16 -6
- package/dist/esm/global-state/project-plugin.js.map +1 -1
- package/dist/esm/index.d.ts +6 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +29 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/kanban/component-generator.d.ts +15 -0
- package/dist/esm/kanban/component-generator.d.ts.map +1 -0
- package/dist/esm/kanban/component-generator.js +17 -0
- package/dist/esm/kanban/component-generator.js.map +1 -0
- package/dist/esm/kanban/project-plugin.d.ts +13 -0
- package/dist/esm/kanban/project-plugin.d.ts.map +1 -0
- package/dist/esm/kanban/project-plugin.js +102 -0
- package/dist/esm/kanban/project-plugin.js.map +1 -0
- package/dist/esm/local-component-path-plugin.d.ts +28 -0
- package/dist/esm/local-component-path-plugin.d.ts.map +1 -0
- package/dist/esm/local-component-path-plugin.js +89 -0
- package/dist/esm/local-component-path-plugin.js.map +1 -0
- package/dist/esm/next-project-mapping.d.ts.map +1 -1
- package/dist/esm/next-project-mapping.js +57 -0
- package/dist/esm/next-project-mapping.js.map +1 -1
- package/dist/esm/rich-text-editor/project-plugin.d.ts.map +1 -1
- package/dist/esm/rich-text-editor/project-plugin.js +2 -106
- package/dist/esm/rich-text-editor/project-plugin.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/uidl-element-traversal.d.ts +18 -0
- package/dist/esm/uidl-element-traversal.d.ts.map +1 -0
- package/dist/esm/uidl-element-traversal.js +125 -0
- package/dist/esm/uidl-element-traversal.js.map +1 -0
- package/package.json +19 -19
- package/src/analytics/project-plugin.ts +145 -0
- package/src/analytics/tracker-component.ts +35 -0
- package/src/analytics/tracker-source.ts +430 -0
- package/src/app-import-injection.ts +46 -0
- package/src/calendar/calendarkit-css.ts +8 -0
- package/src/calendar/project-plugin.ts +48 -0
- package/src/drag-drop/component-generator.ts +245 -0
- package/src/drag-drop/project-plugin.ts +50 -0
- package/src/global-state/project-plugin.ts +15 -7
- package/src/index.ts +35 -0
- package/src/kanban/component-generator.ts +77 -0
- package/src/kanban/project-plugin.ts +63 -0
- package/src/local-component-path-plugin.ts +63 -0
- package/src/next-project-mapping.ts +57 -0
- package/src/rich-text-editor/project-plugin.ts +2 -113
- package/src/uidl-element-traversal.ts +141 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { parse } from '@babel/parser'
|
|
2
|
+
import { FileType, ProjectPluginStructure } from '@teleporthq/teleport-types'
|
|
3
|
+
import { NextAnalyticsProjectPlugin } from '../src/analytics/project-plugin'
|
|
4
|
+
|
|
5
|
+
const APP_CONTENT = `import { GlobalProvider } from '../global-context'
|
|
6
|
+
|
|
7
|
+
const MyApp = ({ Component, pageProps }) => {
|
|
8
|
+
return (
|
|
9
|
+
<GlobalProvider>
|
|
10
|
+
<Component {...pageProps} />
|
|
11
|
+
</GlobalProvider>
|
|
12
|
+
)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default MyApp
|
|
16
|
+
`
|
|
17
|
+
|
|
18
|
+
function makeStructure(analyticsEnabled: boolean): ProjectPluginStructure {
|
|
19
|
+
const files = new Map()
|
|
20
|
+
files.set('_app', {
|
|
21
|
+
path: ['pages'],
|
|
22
|
+
files: [{ name: '_app', fileType: FileType.JS, content: APP_CONTENT }],
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
uidl: {
|
|
27
|
+
name: 'test-project',
|
|
28
|
+
globals: { settings: { title: 'Test', language: 'en' }, assets: [] },
|
|
29
|
+
root: {} as never,
|
|
30
|
+
...(analyticsEnabled ? { analytics: { enabled: true } } : {}),
|
|
31
|
+
},
|
|
32
|
+
files,
|
|
33
|
+
dependencies: {},
|
|
34
|
+
devDependencies: {},
|
|
35
|
+
template: { files: [], subFolders: [] },
|
|
36
|
+
} as unknown as ProjectPluginStructure
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe('NextAnalyticsProjectPlugin', () => {
|
|
40
|
+
it('does nothing when analytics is not enabled', async () => {
|
|
41
|
+
const plugin = new NextAnalyticsProjectPlugin()
|
|
42
|
+
const structure = makeStructure(false)
|
|
43
|
+
|
|
44
|
+
await plugin.runAfter(structure)
|
|
45
|
+
|
|
46
|
+
expect(structure.files.has('teleport-analytics-lib')).toBe(false)
|
|
47
|
+
expect(structure.files.has('teleport-analytics-tracker')).toBe(false)
|
|
48
|
+
|
|
49
|
+
const appFile = structure.files.get('_app').files[0]
|
|
50
|
+
expect(appFile.content).not.toContain('AnalyticsTracker')
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('emits the tracker lib + component and wires the env placeholders', async () => {
|
|
54
|
+
const plugin = new NextAnalyticsProjectPlugin()
|
|
55
|
+
const structure = makeStructure(true)
|
|
56
|
+
|
|
57
|
+
await plugin.runAfter(structure)
|
|
58
|
+
|
|
59
|
+
const lib = structure.files.get('teleport-analytics-lib')
|
|
60
|
+
expect(lib.path).toEqual(['lib'])
|
|
61
|
+
expect(lib.files[0].name).toBe('teleport-analytics')
|
|
62
|
+
expect(lib.files[0].content).toContain('NEXT_PUBLIC_TELEPORT_ANALYTICS_URL')
|
|
63
|
+
expect(lib.files[0].content).toContain('initTeleportAnalytics')
|
|
64
|
+
expect(lib.files[0].content).toContain('sendBeacon')
|
|
65
|
+
expect(lib.files[0].content).toContain("localStorage.getItem('cookieConsent')")
|
|
66
|
+
|
|
67
|
+
// Beacons/batches must use the CORS-safelisted text/plain content type so
|
|
68
|
+
// unload sends are preflight-free and survive a closing tab.
|
|
69
|
+
expect(lib.files[0].content).toContain('text/plain;charset=UTF-8')
|
|
70
|
+
expect(lib.files[0].content).not.toContain("type: 'application/json'")
|
|
71
|
+
expect(lib.files[0].content).not.toContain("'Content-Type': 'application/json'")
|
|
72
|
+
|
|
73
|
+
const tracker = structure.files.get('teleport-analytics-tracker')
|
|
74
|
+
expect(tracker.path).toEqual(['components', 'analytics'])
|
|
75
|
+
expect(tracker.files[0].content).toContain('routeChangeComplete')
|
|
76
|
+
|
|
77
|
+
expect(structure.uidl.globals.env).toEqual({
|
|
78
|
+
NEXT_PUBLIC_TELEPORT_ANALYTICS_URL: 'teleporthq.secrets.NEXT_PUBLIC_TELEPORT_ANALYTICS_URL',
|
|
79
|
+
NEXT_PUBLIC_TELEPORT_ANALYTICS_KEY: 'teleporthq.secrets.NEXT_PUBLIC_TELEPORT_ANALYTICS_KEY',
|
|
80
|
+
})
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('injects <AnalyticsTracker /> into _app as a fragment sibling', async () => {
|
|
84
|
+
const plugin = new NextAnalyticsProjectPlugin()
|
|
85
|
+
const structure = makeStructure(true)
|
|
86
|
+
|
|
87
|
+
await plugin.runAfter(structure)
|
|
88
|
+
|
|
89
|
+
const appFile = structure.files.get('_app').files[0]
|
|
90
|
+
expect(appFile.content).toContain(
|
|
91
|
+
"import AnalyticsTracker from '../components/analytics/AnalyticsTracker'"
|
|
92
|
+
)
|
|
93
|
+
expect(appFile.content).toContain('<AnalyticsTracker /></>')
|
|
94
|
+
expect(appFile.content).toContain('<>')
|
|
95
|
+
// Idempotent: a second run must not double-inject
|
|
96
|
+
await plugin.runAfter(structure)
|
|
97
|
+
const occurrences = appFile.content.split('<AnalyticsTracker />').length - 1
|
|
98
|
+
expect(occurrences).toBe(1)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('produces a still-parseable _app after the string surgery', async () => {
|
|
102
|
+
const plugin = new NextAnalyticsProjectPlugin()
|
|
103
|
+
const structure = makeStructure(true)
|
|
104
|
+
|
|
105
|
+
await plugin.runAfter(structure)
|
|
106
|
+
|
|
107
|
+
const appFile = structure.files.get('_app').files[0]
|
|
108
|
+
// The fragment wrap is raw string manipulation — guarantee it never emits
|
|
109
|
+
// invalid JSX that would silently blank the deployed app.
|
|
110
|
+
expect(() =>
|
|
111
|
+
parse(appFile.content, {
|
|
112
|
+
sourceType: 'module',
|
|
113
|
+
plugins: ['jsx'],
|
|
114
|
+
})
|
|
115
|
+
).not.toThrow()
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it('preserves env values already set by the GUI mapper', async () => {
|
|
119
|
+
const plugin = new NextAnalyticsProjectPlugin()
|
|
120
|
+
const structure = makeStructure(true)
|
|
121
|
+
structure.uidl.globals.env = {
|
|
122
|
+
NEXT_PUBLIC_TELEPORT_ANALYTICS_URL: 'teleporthq.secrets.NEXT_PUBLIC_TELEPORT_ANALYTICS_URL',
|
|
123
|
+
NEXT_PUBLIC_TELEPORT_ANALYTICS_KEY: 'teleporthq.secrets.NEXT_PUBLIC_TELEPORT_ANALYTICS_KEY',
|
|
124
|
+
OTHER_KEY: 'value',
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
await plugin.runAfter(structure)
|
|
128
|
+
|
|
129
|
+
expect(Object.keys(structure.uidl.globals.env)).toHaveLength(3)
|
|
130
|
+
expect(structure.uidl.globals.env.OTHER_KEY).toBe('value')
|
|
131
|
+
})
|
|
132
|
+
})
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { GeneratedFolder, ProjectUIDL } from '@teleporthq/teleport-types'
|
|
2
|
+
import uidlSample from '../../../examples/test-samples/project-sample.json'
|
|
3
|
+
import { createNextProjectGenerator } from '../src'
|
|
4
|
+
import NextTemplate from '../src/project-template'
|
|
5
|
+
|
|
6
|
+
// The production template (react ^17) — the same one the GUI publish path
|
|
7
|
+
// feeds into packProject, so the react bump is asserted against real deps.
|
|
8
|
+
const template = JSON.parse(JSON.stringify(NextTemplate)) as GeneratedFolder
|
|
9
|
+
|
|
10
|
+
const CALENDAR_ELEMENT_NODE = {
|
|
11
|
+
type: 'element',
|
|
12
|
+
content: {
|
|
13
|
+
elementType: 'BasicScheduler',
|
|
14
|
+
name: 'calendar',
|
|
15
|
+
dependency: {
|
|
16
|
+
type: 'package',
|
|
17
|
+
path: 'calendarkit-basic',
|
|
18
|
+
version: '1.1.0',
|
|
19
|
+
meta: { namedImport: true },
|
|
20
|
+
},
|
|
21
|
+
attrs: {
|
|
22
|
+
view: { type: 'static', content: 'month' },
|
|
23
|
+
weekStartsOn: { type: 'static', content: 1 },
|
|
24
|
+
events: {
|
|
25
|
+
type: 'static',
|
|
26
|
+
content: [
|
|
27
|
+
{
|
|
28
|
+
id: '1',
|
|
29
|
+
title: 'Team Meeting',
|
|
30
|
+
start: '2026-06-15T09:00:00',
|
|
31
|
+
end: '2026-06-15T10:30:00',
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
children: [],
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const buildUidlWithCalendar = (): ProjectUIDL => {
|
|
41
|
+
const uidl = JSON.parse(JSON.stringify(uidlSample)) as ProjectUIDL
|
|
42
|
+
const indexPage = (uidl.root.node.content.children || []).find(
|
|
43
|
+
(child) =>
|
|
44
|
+
child.type === 'conditional' && (child.content as { value?: string }).value === 'index'
|
|
45
|
+
)
|
|
46
|
+
const pageElement = (indexPage as { content: { node: { content: { children: unknown[] } } } })
|
|
47
|
+
.content.node.content
|
|
48
|
+
pageElement.children.push(CALENDAR_ELEMENT_NODE)
|
|
49
|
+
return uidl
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const findFile = (folder: GeneratedFolder, folderName: string, fileName: string) =>
|
|
53
|
+
folder.subFolders
|
|
54
|
+
.find((sub) => sub.name === folderName)
|
|
55
|
+
?.files.find((file) => file.name === fileName)
|
|
56
|
+
|
|
57
|
+
describe('Next generator with a CalendarKit calendar element', () => {
|
|
58
|
+
const generator = createNextProjectGenerator()
|
|
59
|
+
|
|
60
|
+
it('generates the calendar page, bumps react and ships the precompiled stylesheet', async () => {
|
|
61
|
+
const outputFolder = await generator.generateProject(buildUidlWithCalendar(), template)
|
|
62
|
+
|
|
63
|
+
const packageFile = outputFolder.files.find((file) => file.name === 'package')
|
|
64
|
+
const packageJson = JSON.parse(packageFile?.content || '{}')
|
|
65
|
+
expect(packageJson.dependencies['calendarkit-basic']).toBe('1.1.0')
|
|
66
|
+
expect(packageJson.dependencies.react).toBe('^18.3.1')
|
|
67
|
+
expect(packageJson.dependencies['react-dom']).toBe('^18.3.1')
|
|
68
|
+
expect(packageJson.dependencies.next).toBe('^12.1.10')
|
|
69
|
+
|
|
70
|
+
const indexPage = findFile(outputFolder, 'pages', 'index')
|
|
71
|
+
expect(indexPage?.content).toContain("import { BasicScheduler } from 'calendarkit-basic'")
|
|
72
|
+
expect(indexPage?.content).toContain('new Date(e.start)')
|
|
73
|
+
expect(indexPage?.content).toContain('new Date(e.end)')
|
|
74
|
+
|
|
75
|
+
const cssFile = findFile(outputFolder, 'pages', 'calendarkit')
|
|
76
|
+
expect(cssFile?.fileType).toBe('css')
|
|
77
|
+
expect(cssFile?.content).toContain(':root')
|
|
78
|
+
expect(cssFile?.content).not.toContain('@tailwind')
|
|
79
|
+
|
|
80
|
+
const appFile = findFile(outputFolder, 'pages', '_app')
|
|
81
|
+
expect(appFile?.content).toContain("import './calendarkit.css'")
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('leaves react untouched for projects without a calendar', async () => {
|
|
85
|
+
const outputFolder = await generator.generateProject(
|
|
86
|
+
JSON.parse(JSON.stringify(uidlSample)) as ProjectUIDL,
|
|
87
|
+
template
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
const packageFile = outputFolder.files.find((file) => file.name === 'package')
|
|
91
|
+
const packageJson = JSON.parse(packageFile?.content || '{}')
|
|
92
|
+
expect(packageJson.dependencies['calendarkit-basic']).toBeUndefined()
|
|
93
|
+
expect(packageJson.dependencies.react).toBe('^17.0.2')
|
|
94
|
+
|
|
95
|
+
const pagesFolder = outputFolder.subFolders.find((sub) => sub.name === 'pages')
|
|
96
|
+
expect(pagesFolder?.files.find((file) => file.name === 'calendarkit')).toBeUndefined()
|
|
97
|
+
})
|
|
98
|
+
})
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { FileType, InMemoryFileRecord, ProjectPluginStructure } from '@teleporthq/teleport-types'
|
|
2
|
+
import { NextCalendarKitProjectPlugin } from '../src/calendar/project-plugin'
|
|
3
|
+
import { CALENDARKIT_CSS } from '../src/calendar/calendarkit-css'
|
|
4
|
+
|
|
5
|
+
const APP_CONTENT = `import './style.css'
|
|
6
|
+
|
|
7
|
+
export default function MyApp({ Component, pageProps }) {
|
|
8
|
+
return <Component {...pageProps} />
|
|
9
|
+
}
|
|
10
|
+
`
|
|
11
|
+
|
|
12
|
+
const buildStructure = (
|
|
13
|
+
dependencies: Record<string, string>,
|
|
14
|
+
withAppFile = true
|
|
15
|
+
): ProjectPluginStructure => {
|
|
16
|
+
const files = new Map<string, InMemoryFileRecord>()
|
|
17
|
+
if (withAppFile) {
|
|
18
|
+
files.set('_app', {
|
|
19
|
+
path: ['pages'],
|
|
20
|
+
files: [{ name: '_app', fileType: FileType.JS, content: APP_CONTENT }],
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
files,
|
|
26
|
+
dependencies,
|
|
27
|
+
devDependencies: {},
|
|
28
|
+
} as unknown as ProjectPluginStructure
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe('NextCalendarKitProjectPlugin', () => {
|
|
32
|
+
const plugin = new NextCalendarKitProjectPlugin()
|
|
33
|
+
|
|
34
|
+
it('is a no-op for projects without calendarkit-basic', async () => {
|
|
35
|
+
const structure = buildStructure({ react: '^17.0.2' })
|
|
36
|
+
|
|
37
|
+
await plugin.runAfter(structure)
|
|
38
|
+
|
|
39
|
+
expect(structure.dependencies.react).toBe('^17.0.2')
|
|
40
|
+
expect(structure.files.has('calendarkit-css')).toBe(false)
|
|
41
|
+
const appFile = structure.files.get('_app')?.files[0]
|
|
42
|
+
expect(appFile?.content).toBe(APP_CONTENT)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('bumps react/react-dom, writes the stylesheet and imports it from _app', async () => {
|
|
46
|
+
const structure = buildStructure({
|
|
47
|
+
react: '^17.0.2',
|
|
48
|
+
'react-dom': '^17.0.2',
|
|
49
|
+
'calendarkit-basic': '1.1.0',
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
await plugin.runAfter(structure)
|
|
53
|
+
|
|
54
|
+
expect(structure.dependencies.react).toBe('^18.3.1')
|
|
55
|
+
expect(structure.dependencies['react-dom']).toBe('^18.3.1')
|
|
56
|
+
|
|
57
|
+
const cssRecord = structure.files.get('calendarkit-css')
|
|
58
|
+
expect(cssRecord?.path).toEqual(['pages'])
|
|
59
|
+
expect(cssRecord?.files[0]).toEqual({
|
|
60
|
+
name: 'calendarkit',
|
|
61
|
+
fileType: FileType.CSS,
|
|
62
|
+
content: CALENDARKIT_CSS,
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
const appContent = structure.files.get('_app')?.files[0].content as string
|
|
66
|
+
expect(appContent).toContain("import './calendarkit.css'")
|
|
67
|
+
expect(appContent.indexOf("import './calendarkit.css'")).toBeLessThan(
|
|
68
|
+
appContent.indexOf("import './style.css'")
|
|
69
|
+
)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('is idempotent when run twice', async () => {
|
|
73
|
+
const structure = buildStructure({ 'calendarkit-basic': '1.1.0' })
|
|
74
|
+
|
|
75
|
+
await plugin.runAfter(structure)
|
|
76
|
+
await plugin.runAfter(structure)
|
|
77
|
+
|
|
78
|
+
const appContent = structure.files.get('_app')?.files[0].content as string
|
|
79
|
+
expect(appContent.match(/import '\.\/calendarkit\.css'/g)).toHaveLength(1)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('still adds the stylesheet when no _app file exists', async () => {
|
|
83
|
+
const structure = buildStructure({ 'calendarkit-basic': '1.1.0' }, false)
|
|
84
|
+
|
|
85
|
+
await plugin.runAfter(structure)
|
|
86
|
+
|
|
87
|
+
expect(structure.files.has('calendarkit-css')).toBe(true)
|
|
88
|
+
expect(structure.dependencies.react).toBe('^18.3.1')
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('ships CSS with no tailwind directives and no global resets', () => {
|
|
92
|
+
expect(CALENDARKIT_CSS).not.toContain('@tailwind')
|
|
93
|
+
expect(CALENDARKIT_CSS).not.toMatch(/(^|\})\*\{/)
|
|
94
|
+
expect(CALENDARKIT_CSS).not.toMatch(/(^|\})body\{/)
|
|
95
|
+
expect(CALENDARKIT_CSS).toContain(':root')
|
|
96
|
+
expect(CALENDARKIT_CSS).toContain('.dark')
|
|
97
|
+
expect(CALENDARKIT_CSS).toContain('grid-cols-7')
|
|
98
|
+
})
|
|
99
|
+
})
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { GeneratedFolder, ProjectUIDL } from '@teleporthq/teleport-types'
|
|
2
|
+
import uidlSample from '../../../examples/test-samples/project-sample.json'
|
|
3
|
+
import { createNextProjectGenerator } from '../src'
|
|
4
|
+
import NextTemplate from '../src/project-template'
|
|
5
|
+
|
|
6
|
+
// The production template (react ^17) — the same one the GUI publish path
|
|
7
|
+
// feeds into packProject.
|
|
8
|
+
const template = JSON.parse(JSON.stringify(NextTemplate)) as GeneratedFolder
|
|
9
|
+
|
|
10
|
+
const textChild = (content: string) => ({ type: 'static', content })
|
|
11
|
+
|
|
12
|
+
const DRAG_AREA_NODE = {
|
|
13
|
+
type: 'element',
|
|
14
|
+
content: {
|
|
15
|
+
elementType: 'thq-drag-area',
|
|
16
|
+
name: 'tasks-drag-area',
|
|
17
|
+
children: [
|
|
18
|
+
{
|
|
19
|
+
type: 'element',
|
|
20
|
+
content: {
|
|
21
|
+
elementType: 'thq-droppable',
|
|
22
|
+
name: 'todo-zone',
|
|
23
|
+
attrs: { dropId: { type: 'static', content: 'todo' } },
|
|
24
|
+
children: [
|
|
25
|
+
{
|
|
26
|
+
type: 'element',
|
|
27
|
+
content: {
|
|
28
|
+
elementType: 'thq-draggable',
|
|
29
|
+
name: 'task-card',
|
|
30
|
+
attrs: { dragId: { type: 'static', content: 'task-1' } },
|
|
31
|
+
children: [textChild('Write the report')],
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
type: 'element',
|
|
39
|
+
content: {
|
|
40
|
+
elementType: 'thq-droppable',
|
|
41
|
+
name: 'done-zone',
|
|
42
|
+
attrs: { dropId: { type: 'static', content: 'done' } },
|
|
43
|
+
children: [],
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const SORTABLE_NODE = {
|
|
51
|
+
type: 'element',
|
|
52
|
+
content: {
|
|
53
|
+
elementType: 'thq-sortable',
|
|
54
|
+
name: 'priority-list',
|
|
55
|
+
attrs: { direction: { type: 'static', content: 'vertical' } },
|
|
56
|
+
children: [
|
|
57
|
+
{
|
|
58
|
+
type: 'element',
|
|
59
|
+
content: {
|
|
60
|
+
elementType: 'thq-sortable-item',
|
|
61
|
+
name: 'priority-item',
|
|
62
|
+
children: [textChild('First priority')],
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
type: 'element',
|
|
67
|
+
content: {
|
|
68
|
+
elementType: 'thq-sortable-item',
|
|
69
|
+
name: 'priority-item-2',
|
|
70
|
+
children: [textChild('Second priority')],
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const KANBAN_NODE = {
|
|
78
|
+
type: 'element',
|
|
79
|
+
content: {
|
|
80
|
+
elementType: 'kanban-node',
|
|
81
|
+
name: 'project-board',
|
|
82
|
+
attrs: {
|
|
83
|
+
board: {
|
|
84
|
+
type: 'static',
|
|
85
|
+
content: {
|
|
86
|
+
columns: [
|
|
87
|
+
{
|
|
88
|
+
id: 'todo',
|
|
89
|
+
title: 'To Do',
|
|
90
|
+
cards: [{ id: '1', title: 'Design review', description: 'Review mockups' }],
|
|
91
|
+
},
|
|
92
|
+
{ id: 'done', title: 'Done', cards: [] },
|
|
93
|
+
],
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
disableColumnDrag: { type: 'static', content: true },
|
|
97
|
+
},
|
|
98
|
+
children: [],
|
|
99
|
+
},
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const buildUidl = (): ProjectUIDL => {
|
|
103
|
+
const uidl = JSON.parse(JSON.stringify(uidlSample)) as ProjectUIDL
|
|
104
|
+
const indexPage = (uidl.root.node.content.children || []).find(
|
|
105
|
+
(child) =>
|
|
106
|
+
child.type === 'conditional' && (child.content as { value?: string }).value === 'index'
|
|
107
|
+
)
|
|
108
|
+
const pageElement = (indexPage as { content: { node: { content: { children: unknown[] } } } })
|
|
109
|
+
.content.node.content
|
|
110
|
+
pageElement.children.push(DRAG_AREA_NODE, SORTABLE_NODE, KANBAN_NODE)
|
|
111
|
+
return uidl
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const findFile = (folder: GeneratedFolder, folderName: string, fileName: string) =>
|
|
115
|
+
folder.subFolders
|
|
116
|
+
.find((sub) => sub.name === folderName)
|
|
117
|
+
?.files.find((file) => file.name === fileName)
|
|
118
|
+
|
|
119
|
+
describe('Next generator with drag-and-drop and kanban primitives', () => {
|
|
120
|
+
const generator = createNextProjectGenerator()
|
|
121
|
+
|
|
122
|
+
it('generates the wrapper components, page imports and dependencies', async () => {
|
|
123
|
+
const outputFolder = await generator.generateProject(buildUidl(), template)
|
|
124
|
+
|
|
125
|
+
const packageFile = outputFolder.files.find((file) => file.name === 'package')
|
|
126
|
+
const packageJson = JSON.parse(packageFile?.content || '{}')
|
|
127
|
+
expect(packageJson.dependencies['@dnd-kit/core']).toBe('^6.3.1')
|
|
128
|
+
expect(packageJson.dependencies['@dnd-kit/sortable']).toBe('^10.0.0')
|
|
129
|
+
expect(packageJson.dependencies['@dnd-kit/utilities']).toBe('^3.2.2')
|
|
130
|
+
expect(packageJson.dependencies['@asseinfo/react-kanban']).toBe('2.2.0')
|
|
131
|
+
// Neither library requires a react bump on its own.
|
|
132
|
+
expect(packageJson.dependencies.react).toBe('^17.0.2')
|
|
133
|
+
|
|
134
|
+
const dragDropComponent = findFile(outputFolder, 'components', 'tq-drag-drop')
|
|
135
|
+
expect(dragDropComponent?.content).toContain('export const TqDragArea')
|
|
136
|
+
expect(dragDropComponent?.content).toContain("from '@dnd-kit/core'")
|
|
137
|
+
|
|
138
|
+
const kanbanComponent = findFile(outputFolder, 'components', 'tq-kanban')
|
|
139
|
+
expect(kanbanComponent?.content).toContain('initialBoard')
|
|
140
|
+
expect(kanbanComponent?.content).toContain('ssr: false')
|
|
141
|
+
|
|
142
|
+
const indexPage = findFile(outputFolder, 'pages', 'index')
|
|
143
|
+
expect(indexPage?.content).toContain('TqDragArea')
|
|
144
|
+
expect(indexPage?.content).toContain('TqDraggable')
|
|
145
|
+
expect(indexPage?.content).toContain('TqDroppable')
|
|
146
|
+
expect(indexPage?.content).toContain('TqSortable')
|
|
147
|
+
expect(indexPage?.content).toContain("from '../components/tq-drag-drop'")
|
|
148
|
+
expect(indexPage?.content).toContain('TqKanban')
|
|
149
|
+
expect(indexPage?.content).toContain("from '../components/tq-kanban'")
|
|
150
|
+
expect(indexPage?.content).toContain('dragId="task-1"')
|
|
151
|
+
expect(indexPage?.content).toContain('dropId="todo"')
|
|
152
|
+
|
|
153
|
+
const npmrc = outputFolder.files.find((file) => file.name === '.npmrc')
|
|
154
|
+
expect(npmrc?.content).toContain('legacy-peer-deps=true')
|
|
155
|
+
|
|
156
|
+
const appFile = findFile(outputFolder, 'pages', '_app')
|
|
157
|
+
expect(appFile?.content).toContain("import '@asseinfo/react-kanban/dist/styles.css'")
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
it('emits none of the wrappers for projects without these primitives', async () => {
|
|
161
|
+
const outputFolder = await generator.generateProject(
|
|
162
|
+
JSON.parse(JSON.stringify(uidlSample)) as ProjectUIDL,
|
|
163
|
+
JSON.parse(JSON.stringify(NextTemplate)) as GeneratedFolder
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
const componentsFolder = outputFolder.subFolders.find((sub) => sub.name === 'components')
|
|
167
|
+
expect(componentsFolder?.files.find((file) => file.name === 'tq-drag-drop')).toBeUndefined()
|
|
168
|
+
expect(componentsFolder?.files.find((file) => file.name === 'tq-kanban')).toBeUndefined()
|
|
169
|
+
expect(outputFolder.files.find((file) => file.name === '.npmrc')).toBeUndefined()
|
|
170
|
+
|
|
171
|
+
const packageFile = outputFolder.files.find((file) => file.name === 'package')
|
|
172
|
+
const packageJson = JSON.parse(packageFile?.content || '{}')
|
|
173
|
+
expect(packageJson.dependencies['@dnd-kit/core']).toBeUndefined()
|
|
174
|
+
expect(packageJson.dependencies['@asseinfo/react-kanban']).toBeUndefined()
|
|
175
|
+
})
|
|
176
|
+
})
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FileType,
|
|
3
|
+
InMemoryFileRecord,
|
|
4
|
+
ProjectPluginStructure,
|
|
5
|
+
ProjectUIDL,
|
|
6
|
+
UIDLElementNode,
|
|
7
|
+
} from '@teleporthq/teleport-types'
|
|
8
|
+
import { NextDragDropProjectPlugin } from '../src/drag-drop/project-plugin'
|
|
9
|
+
import { NextKanbanProjectPlugin } from '../src/kanban/project-plugin'
|
|
10
|
+
|
|
11
|
+
const APP_CONTENT = `import './style.css'
|
|
12
|
+
|
|
13
|
+
export default function MyApp({ Component, pageProps }) {
|
|
14
|
+
return <Component {...pageProps} />
|
|
15
|
+
}
|
|
16
|
+
`
|
|
17
|
+
|
|
18
|
+
const elementNode = (elementType: string, children: UIDLElementNode[] = []): UIDLElementNode => ({
|
|
19
|
+
type: 'element',
|
|
20
|
+
content: {
|
|
21
|
+
elementType,
|
|
22
|
+
children,
|
|
23
|
+
},
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
const buildStructure = (pageChildren: UIDLElementNode[]): ProjectPluginStructure => {
|
|
27
|
+
const files = new Map<string, InMemoryFileRecord>()
|
|
28
|
+
files.set('_app', {
|
|
29
|
+
path: ['pages'],
|
|
30
|
+
files: [{ name: '_app', fileType: FileType.JS, content: APP_CONTENT }],
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
const uidl = {
|
|
34
|
+
name: 'test',
|
|
35
|
+
root: {
|
|
36
|
+
name: 'App',
|
|
37
|
+
node: elementNode('container', pageChildren),
|
|
38
|
+
},
|
|
39
|
+
components: {},
|
|
40
|
+
} as unknown as ProjectUIDL
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
uidl,
|
|
44
|
+
files,
|
|
45
|
+
dependencies: {},
|
|
46
|
+
devDependencies: {},
|
|
47
|
+
} as unknown as ProjectPluginStructure
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
describe('NextDragDropProjectPlugin', () => {
|
|
51
|
+
const plugin = new NextDragDropProjectPlugin()
|
|
52
|
+
|
|
53
|
+
it('is a no-op for projects without drag-and-drop primitives', async () => {
|
|
54
|
+
const structure = buildStructure([elementNode('container')])
|
|
55
|
+
|
|
56
|
+
await plugin.runAfter(structure)
|
|
57
|
+
|
|
58
|
+
expect(structure.files.has('tq-drag-drop-component')).toBe(false)
|
|
59
|
+
expect(structure.dependencies['@dnd-kit/core']).toBeUndefined()
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it.each(['thq-drag-area', 'thq-draggable', 'thq-droppable', 'thq-sortable', 'thq-sortable-item'])(
|
|
63
|
+
'emits the wrapper file and dnd-kit deps when %s is used',
|
|
64
|
+
async (primitive) => {
|
|
65
|
+
const structure = buildStructure([elementNode(primitive)])
|
|
66
|
+
|
|
67
|
+
await plugin.runAfter(structure)
|
|
68
|
+
|
|
69
|
+
const record = structure.files.get('tq-drag-drop-component')
|
|
70
|
+
expect(record?.path).toEqual(['components'])
|
|
71
|
+
expect(record?.files[0].name).toBe('tq-drag-drop')
|
|
72
|
+
expect(record?.files[0].content).toContain('export const TqDragArea')
|
|
73
|
+
expect(record?.files[0].content).toContain('export const TqSortable')
|
|
74
|
+
expect(structure.dependencies['@dnd-kit/core']).toBe('^6.3.1')
|
|
75
|
+
expect(structure.dependencies['@dnd-kit/sortable']).toBe('^10.0.0')
|
|
76
|
+
expect(structure.dependencies['@dnd-kit/utilities']).toBe('^3.2.2')
|
|
77
|
+
}
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
it('detects primitives nested deep in the tree', async () => {
|
|
81
|
+
const structure = buildStructure([
|
|
82
|
+
elementNode('container', [elementNode('container', [elementNode('thq-sortable')])]),
|
|
83
|
+
])
|
|
84
|
+
|
|
85
|
+
await plugin.runAfter(structure)
|
|
86
|
+
|
|
87
|
+
expect(structure.files.has('tq-drag-drop-component')).toBe(true)
|
|
88
|
+
})
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
describe('NextKanbanProjectPlugin', () => {
|
|
92
|
+
const plugin = new NextKanbanProjectPlugin()
|
|
93
|
+
|
|
94
|
+
it('is a no-op for projects without a kanban board', async () => {
|
|
95
|
+
const structure = buildStructure([elementNode('container')])
|
|
96
|
+
|
|
97
|
+
await plugin.runAfter(structure)
|
|
98
|
+
|
|
99
|
+
expect(structure.files.has('tq-kanban-component')).toBe(false)
|
|
100
|
+
expect(structure.files.has('tq-kanban-npmrc')).toBe(false)
|
|
101
|
+
expect(structure.dependencies['@asseinfo/react-kanban']).toBeUndefined()
|
|
102
|
+
const appContent = structure.files.get('_app')?.files[0].content as string
|
|
103
|
+
expect(appContent).toBe(APP_CONTENT)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('emits the wrapper, dependency, stylesheet import and .npmrc when used', async () => {
|
|
107
|
+
const structure = buildStructure([elementNode('kanban-node')])
|
|
108
|
+
|
|
109
|
+
await plugin.runAfter(structure)
|
|
110
|
+
|
|
111
|
+
const componentRecord = structure.files.get('tq-kanban-component')
|
|
112
|
+
expect(componentRecord?.path).toEqual(['components'])
|
|
113
|
+
expect(componentRecord?.files[0].name).toBe('tq-kanban')
|
|
114
|
+
expect(componentRecord?.files[0].content).toContain(
|
|
115
|
+
"dynamic(() => import('@asseinfo/react-kanban')"
|
|
116
|
+
)
|
|
117
|
+
expect(componentRecord?.files[0].content).toContain('ssr: false')
|
|
118
|
+
expect(componentRecord?.files[0].content).toContain('initialBoard')
|
|
119
|
+
|
|
120
|
+
const npmrcRecord = structure.files.get('tq-kanban-npmrc')
|
|
121
|
+
expect(npmrcRecord?.path).toEqual([])
|
|
122
|
+
expect(npmrcRecord?.files[0].name).toBe('.npmrc')
|
|
123
|
+
expect(npmrcRecord?.files[0].fileType).toBeUndefined()
|
|
124
|
+
expect(npmrcRecord?.files[0].content).toContain('legacy-peer-deps=true')
|
|
125
|
+
|
|
126
|
+
expect(structure.dependencies['@asseinfo/react-kanban']).toBe('2.2.0')
|
|
127
|
+
|
|
128
|
+
const appContent = structure.files.get('_app')?.files[0].content as string
|
|
129
|
+
expect(appContent).toContain("import '@asseinfo/react-kanban/dist/styles.css'")
|
|
130
|
+
expect(appContent.indexOf('@asseinfo/react-kanban/dist/styles.css')).toBeLessThan(
|
|
131
|
+
appContent.indexOf('./style.css')
|
|
132
|
+
)
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it('is idempotent when run twice', async () => {
|
|
136
|
+
const structure = buildStructure([elementNode('kanban-node')])
|
|
137
|
+
|
|
138
|
+
await plugin.runAfter(structure)
|
|
139
|
+
await plugin.runAfter(structure)
|
|
140
|
+
|
|
141
|
+
const appContent = structure.files.get('_app')?.files[0].content as string
|
|
142
|
+
expect(appContent.match(/@asseinfo\/react-kanban\/dist\/styles\.css/g)).toHaveLength(1)
|
|
143
|
+
})
|
|
144
|
+
})
|