@teleporthq/teleport-project-generator-next 0.43.69 → 0.43.71
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__/body-code-end2end.test.ts +71 -0
- package/__tests__/body-code-unpack.test.ts +101 -0
- package/__tests__/custom-property-styles.test.ts +37 -0
- package/__tests__/document-inline-script-guard.test.ts +58 -0
- package/__tests__/emitted-components-rules-of-hooks.test.ts +10 -1
- package/__tests__/motion-end2end.test.ts +70 -0
- package/__tests__/motion-stagger-repeater.test.ts +8 -4
- package/__tests__/page-transition-end2end.test.ts +619 -0
- package/__tests__/pagination-scroll-runtime.test.ts +9 -1
- package/__tests__/scroll-scene-chapter-index.test.ts +35 -0
- package/__tests__/scroll-scene-end2end.test.ts +292 -0
- package/__tests__/scroll-scene-moment.test.ts +46 -0
- package/__tests__/scroll-scene-points.test.ts +49 -0
- package/__tests__/scroll-video-end2end.test.ts +146 -0
- package/__tests__/widget-project-plugins.test.ts +24 -5
- package/dist/cjs/body-code/body-code-component.d.ts +37 -0
- package/dist/cjs/body-code/body-code-component.d.ts.map +1 -0
- package/dist/cjs/body-code/body-code-component.js +50 -0
- package/dist/cjs/body-code/body-code-component.js.map +1 -0
- package/dist/cjs/body-code/project-plugin.d.ts +11 -0
- package/dist/cjs/body-code/project-plugin.d.ts.map +1 -0
- package/dist/cjs/body-code/project-plugin.js +86 -0
- package/dist/cjs/body-code/project-plugin.js.map +1 -0
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +30 -22
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/local-component-path-plugin.d.ts.map +1 -1
- package/dist/cjs/local-component-path-plugin.js +2 -0
- package/dist/cjs/local-component-path-plugin.js.map +1 -1
- package/dist/cjs/next-project-mapping.d.ts.map +1 -1
- package/dist/cjs/next-project-mapping.js +14 -0
- package/dist/cjs/next-project-mapping.js.map +1 -1
- package/dist/cjs/page-transition/page-transition-component.d.ts +55 -0
- package/dist/cjs/page-transition/page-transition-component.d.ts.map +1 -0
- package/dist/cjs/page-transition/page-transition-component.js +92 -0
- package/dist/cjs/page-transition/page-transition-component.js.map +1 -0
- package/dist/cjs/page-transition/page-transition-variants.d.ts +16 -0
- package/dist/cjs/page-transition/page-transition-variants.d.ts.map +1 -0
- package/dist/cjs/page-transition/page-transition-variants.js +14 -0
- package/dist/cjs/page-transition/page-transition-variants.js.map +1 -0
- package/dist/cjs/page-transition/project-plugin.d.ts +20 -0
- package/dist/cjs/page-transition/project-plugin.d.ts.map +1 -0
- package/dist/cjs/page-transition/project-plugin.js +118 -0
- package/dist/cjs/page-transition/project-plugin.js.map +1 -0
- package/dist/cjs/page-transition/reveal-on-screen.d.ts +36 -0
- package/dist/cjs/page-transition/reveal-on-screen.d.ts.map +1 -0
- package/dist/cjs/page-transition/reveal-on-screen.js +77 -0
- package/dist/cjs/page-transition/reveal-on-screen.js.map +1 -0
- package/dist/cjs/pagination-scroll/pagination-scroll-runtime.d.ts.map +1 -1
- package/dist/cjs/pagination-scroll/pagination-scroll-runtime.js +1 -1
- package/dist/cjs/pagination-scroll/pagination-scroll-runtime.js.map +1 -1
- package/dist/cjs/scroll-rail/project-plugin.d.ts +14 -0
- package/dist/cjs/scroll-rail/project-plugin.d.ts.map +1 -0
- package/dist/cjs/scroll-rail/project-plugin.js +98 -0
- package/dist/cjs/scroll-rail/project-plugin.js.map +1 -0
- package/dist/cjs/scroll-rail/scroll-rail-component.d.ts +21 -0
- package/dist/cjs/scroll-rail/scroll-rail-component.d.ts.map +1 -0
- package/dist/cjs/scroll-rail/scroll-rail-component.js +31 -0
- package/dist/cjs/scroll-rail/scroll-rail-component.js.map +1 -0
- package/dist/cjs/snap-into-view/project-plugin.d.ts +14 -0
- package/dist/cjs/snap-into-view/project-plugin.d.ts.map +1 -0
- package/dist/cjs/snap-into-view/project-plugin.js +101 -0
- package/dist/cjs/snap-into-view/project-plugin.js.map +1 -0
- package/dist/cjs/snap-into-view/snap-into-view-component.d.ts +16 -0
- package/dist/cjs/snap-into-view/snap-into-view-component.d.ts.map +1 -0
- package/dist/cjs/snap-into-view/snap-into-view-component.js +22 -0
- package/dist/cjs/snap-into-view/snap-into-view-component.js.map +1 -0
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/cjs/utils.d.ts.map +1 -1
- package/dist/cjs/utils.js +13 -1
- package/dist/cjs/utils.js.map +1 -1
- package/dist/cjs/widgets/index.d.ts.map +1 -1
- package/dist/cjs/widgets/index.js +18 -0
- package/dist/cjs/widgets/index.js.map +1 -1
- package/dist/cjs/widgets/motion-component.d.ts +4 -3
- package/dist/cjs/widgets/motion-component.d.ts.map +1 -1
- package/dist/cjs/widgets/motion-component.js +6 -4
- package/dist/cjs/widgets/motion-component.js.map +1 -1
- package/dist/cjs/widgets/scroll-scene-chapter-index.d.ts +3 -0
- package/dist/cjs/widgets/scroll-scene-chapter-index.d.ts.map +1 -0
- package/dist/cjs/widgets/scroll-scene-chapter-index.js +6 -0
- package/dist/cjs/widgets/scroll-scene-chapter-index.js.map +1 -0
- package/dist/cjs/widgets/scroll-scene-component.d.ts +20 -0
- package/dist/cjs/widgets/scroll-scene-component.d.ts.map +1 -0
- package/dist/cjs/widgets/scroll-scene-component.js +27 -0
- package/dist/cjs/widgets/scroll-scene-component.js.map +1 -0
- package/dist/cjs/widgets/scroll-scene-moment.d.ts +4 -0
- package/dist/cjs/widgets/scroll-scene-moment.d.ts.map +1 -0
- package/dist/cjs/widgets/scroll-scene-moment.js +6 -0
- package/dist/cjs/widgets/scroll-scene-moment.js.map +1 -0
- package/dist/cjs/widgets/scroll-scene-points.d.ts +5 -0
- package/dist/cjs/widgets/scroll-scene-points.d.ts.map +1 -0
- package/dist/cjs/widgets/scroll-scene-points.js +8 -0
- package/dist/cjs/widgets/scroll-scene-points.js.map +1 -0
- package/dist/cjs/widgets/scroll-scene-unclip.d.ts +3 -0
- package/dist/cjs/widgets/scroll-scene-unclip.d.ts.map +1 -0
- package/dist/cjs/widgets/scroll-scene-unclip.js +6 -0
- package/dist/cjs/widgets/scroll-scene-unclip.js.map +1 -0
- package/dist/cjs/widgets/scroll-video-component.d.ts +25 -0
- package/dist/cjs/widgets/scroll-video-component.d.ts.map +1 -0
- package/dist/cjs/widgets/scroll-video-component.js +32 -0
- package/dist/cjs/widgets/scroll-video-component.js.map +1 -0
- package/dist/esm/body-code/body-code-component.d.ts +37 -0
- package/dist/esm/body-code/body-code-component.d.ts.map +1 -0
- package/dist/esm/body-code/body-code-component.js +44 -0
- package/dist/esm/body-code/body-code-component.js.map +1 -0
- package/dist/esm/body-code/project-plugin.d.ts +11 -0
- package/dist/esm/body-code/project-plugin.d.ts.map +1 -0
- package/dist/esm/body-code/project-plugin.js +83 -0
- package/dist/esm/body-code/project-plugin.js.map +1 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +8 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/local-component-path-plugin.d.ts.map +1 -1
- package/dist/esm/local-component-path-plugin.js +2 -0
- package/dist/esm/local-component-path-plugin.js.map +1 -1
- package/dist/esm/next-project-mapping.d.ts.map +1 -1
- package/dist/esm/next-project-mapping.js +14 -0
- package/dist/esm/next-project-mapping.js.map +1 -1
- package/dist/esm/page-transition/page-transition-component.d.ts +55 -0
- package/dist/esm/page-transition/page-transition-component.d.ts.map +1 -0
- package/dist/esm/page-transition/page-transition-component.js +88 -0
- package/dist/esm/page-transition/page-transition-component.js.map +1 -0
- package/dist/esm/page-transition/page-transition-variants.d.ts +16 -0
- package/dist/esm/page-transition/page-transition-variants.d.ts.map +1 -0
- package/dist/esm/page-transition/page-transition-variants.js +11 -0
- package/dist/esm/page-transition/page-transition-variants.js.map +1 -0
- package/dist/esm/page-transition/project-plugin.d.ts +20 -0
- package/dist/esm/page-transition/project-plugin.d.ts.map +1 -0
- package/dist/esm/page-transition/project-plugin.js +115 -0
- package/dist/esm/page-transition/project-plugin.js.map +1 -0
- package/dist/esm/page-transition/reveal-on-screen.d.ts +36 -0
- package/dist/esm/page-transition/reveal-on-screen.d.ts.map +1 -0
- package/dist/esm/page-transition/reveal-on-screen.js +73 -0
- package/dist/esm/page-transition/reveal-on-screen.js.map +1 -0
- package/dist/esm/pagination-scroll/pagination-scroll-runtime.d.ts.map +1 -1
- package/dist/esm/pagination-scroll/pagination-scroll-runtime.js +1 -1
- package/dist/esm/pagination-scroll/pagination-scroll-runtime.js.map +1 -1
- package/dist/esm/scroll-rail/project-plugin.d.ts +14 -0
- package/dist/esm/scroll-rail/project-plugin.d.ts.map +1 -0
- package/dist/esm/scroll-rail/project-plugin.js +94 -0
- package/dist/esm/scroll-rail/project-plugin.js.map +1 -0
- package/dist/esm/scroll-rail/scroll-rail-component.d.ts +21 -0
- package/dist/esm/scroll-rail/scroll-rail-component.d.ts.map +1 -0
- package/dist/esm/scroll-rail/scroll-rail-component.js +27 -0
- package/dist/esm/scroll-rail/scroll-rail-component.js.map +1 -0
- package/dist/esm/snap-into-view/project-plugin.d.ts +14 -0
- package/dist/esm/snap-into-view/project-plugin.d.ts.map +1 -0
- package/dist/esm/snap-into-view/project-plugin.js +97 -0
- package/dist/esm/snap-into-view/project-plugin.js.map +1 -0
- package/dist/esm/snap-into-view/snap-into-view-component.d.ts +16 -0
- package/dist/esm/snap-into-view/snap-into-view-component.d.ts.map +1 -0
- package/dist/esm/snap-into-view/snap-into-view-component.js +18 -0
- package/dist/esm/snap-into-view/snap-into-view-component.js.map +1 -0
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/utils.d.ts.map +1 -1
- package/dist/esm/utils.js +14 -2
- package/dist/esm/utils.js.map +1 -1
- package/dist/esm/widgets/index.d.ts.map +1 -1
- package/dist/esm/widgets/index.js +18 -0
- package/dist/esm/widgets/index.js.map +1 -1
- package/dist/esm/widgets/motion-component.d.ts +4 -3
- package/dist/esm/widgets/motion-component.d.ts.map +1 -1
- package/dist/esm/widgets/motion-component.js +6 -4
- package/dist/esm/widgets/motion-component.js.map +1 -1
- package/dist/esm/widgets/scroll-scene-chapter-index.d.ts +3 -0
- package/dist/esm/widgets/scroll-scene-chapter-index.d.ts.map +1 -0
- package/dist/esm/widgets/scroll-scene-chapter-index.js +3 -0
- package/dist/esm/widgets/scroll-scene-chapter-index.js.map +1 -0
- package/dist/esm/widgets/scroll-scene-component.d.ts +20 -0
- package/dist/esm/widgets/scroll-scene-component.d.ts.map +1 -0
- package/dist/esm/widgets/scroll-scene-component.js +23 -0
- package/dist/esm/widgets/scroll-scene-component.js.map +1 -0
- package/dist/esm/widgets/scroll-scene-moment.d.ts +4 -0
- package/dist/esm/widgets/scroll-scene-moment.d.ts.map +1 -0
- package/dist/esm/widgets/scroll-scene-moment.js +3 -0
- package/dist/esm/widgets/scroll-scene-moment.js.map +1 -0
- package/dist/esm/widgets/scroll-scene-points.d.ts +5 -0
- package/dist/esm/widgets/scroll-scene-points.d.ts.map +1 -0
- package/dist/esm/widgets/scroll-scene-points.js +5 -0
- package/dist/esm/widgets/scroll-scene-points.js.map +1 -0
- package/dist/esm/widgets/scroll-scene-unclip.d.ts +3 -0
- package/dist/esm/widgets/scroll-scene-unclip.d.ts.map +1 -0
- package/dist/esm/widgets/scroll-scene-unclip.js +3 -0
- package/dist/esm/widgets/scroll-scene-unclip.js.map +1 -0
- package/dist/esm/widgets/scroll-video-component.d.ts +25 -0
- package/dist/esm/widgets/scroll-video-component.d.ts.map +1 -0
- package/dist/esm/widgets/scroll-video-component.js +28 -0
- package/dist/esm/widgets/scroll-video-component.js.map +1 -0
- package/package.json +19 -19
- package/src/body-code/body-code-component.ts +79 -0
- package/src/body-code/project-plugin.ts +35 -0
- package/src/index.ts +8 -0
- package/src/local-component-path-plugin.ts +2 -0
- package/src/next-project-mapping.ts +14 -0
- package/src/page-transition/page-transition-component.ts +479 -0
- package/src/page-transition/page-transition-variants.ts +18 -0
- package/src/page-transition/project-plugin.ts +73 -0
- package/src/page-transition/reveal-on-screen.ts +89 -0
- package/src/pagination-scroll/pagination-scroll-runtime.ts +15 -3
- package/src/scroll-rail/project-plugin.ts +48 -0
- package/src/scroll-rail/scroll-rail-component.ts +174 -0
- package/src/snap-into-view/project-plugin.ts +50 -0
- package/src/snap-into-view/snap-into-view-component.ts +110 -0
- package/src/utils.ts +15 -2
- package/src/widgets/index.ts +18 -0
- package/src/widgets/motion-component.ts +82 -135
- package/src/widgets/scroll-scene-chapter-index.ts +3 -0
- package/src/widgets/scroll-scene-component.ts +348 -0
- package/src/widgets/scroll-scene-moment.ts +4 -0
- package/src/widgets/scroll-scene-points.ts +5 -0
- package/src/widgets/scroll-scene-unclip.ts +3 -0
- package/src/widgets/scroll-video-component.ts +219 -0
|
@@ -0,0 +1,71 @@
|
|
|
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
|
+
import {
|
|
6
|
+
BODY_CODE_TEMPLATE_ATTR,
|
|
7
|
+
splitBodyCustomCode,
|
|
8
|
+
wrapBodyCustomCode,
|
|
9
|
+
} from '../src/body-code/body-code-component'
|
|
10
|
+
|
|
11
|
+
const template = JSON.parse(JSON.stringify(NextTemplate)) as GeneratedFolder
|
|
12
|
+
|
|
13
|
+
const BODY_CODE = [
|
|
14
|
+
'<script src="https://unpkg.com/@teleporthq/teleport-custom-scripts"></script>',
|
|
15
|
+
'<noscript><img src="https://px.example/pixel" alt="" /></noscript>',
|
|
16
|
+
'<script>window.__patched = true</script>',
|
|
17
|
+
].join('\n')
|
|
18
|
+
|
|
19
|
+
const buildUidlWithBodyCode = (body: string | undefined): ProjectUIDL => {
|
|
20
|
+
const uidl = JSON.parse(JSON.stringify(uidlSample)) as ProjectUIDL
|
|
21
|
+
uidl.globals.customCode = body === undefined ? undefined : { body }
|
|
22
|
+
return uidl
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const findFile = (folder: GeneratedFolder, folderName: string, fileName: string) =>
|
|
26
|
+
folder.subFolders
|
|
27
|
+
.find((sub) => sub.name === folderName)
|
|
28
|
+
?.files.find((file) => file.name === fileName)
|
|
29
|
+
|
|
30
|
+
describe('body custom code runs after hydration', () => {
|
|
31
|
+
it('keeps noscript live and makes everything else inert', () => {
|
|
32
|
+
const { noscript, deferred } = splitBodyCustomCode(BODY_CODE)
|
|
33
|
+
expect(noscript).toBe('<noscript><img src="https://px.example/pixel" alt="" /></noscript>')
|
|
34
|
+
expect(deferred).not.toContain('<noscript')
|
|
35
|
+
expect(deferred).toContain('teleport-custom-scripts')
|
|
36
|
+
expect(deferred).toContain('window.__patched')
|
|
37
|
+
|
|
38
|
+
const html = wrapBodyCustomCode(BODY_CODE)
|
|
39
|
+
expect(html.indexOf('<noscript')).toBeLessThan(html.indexOf('<template'))
|
|
40
|
+
expect(html).toContain(`<template ${BODY_CODE_TEMPLATE_ATTR}>`)
|
|
41
|
+
// a body made only of noscript ships no template at all
|
|
42
|
+
expect(wrapBodyCustomCode('<noscript>x</noscript>')).toBe('<noscript>x</noscript>')
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
describe('generated project', () => {
|
|
46
|
+
const generator = createNextProjectGenerator()
|
|
47
|
+
|
|
48
|
+
it('ships the code inert in _document and the unpacker as an _app sibling', async () => {
|
|
49
|
+
const output = await generator.generateProject(buildUidlWithBodyCode(BODY_CODE), template)
|
|
50
|
+
|
|
51
|
+
const document = findFile(output, 'pages', '_document')
|
|
52
|
+
expect(document?.content).toContain(`<template ${BODY_CODE_TEMPLATE_ATTR}>`)
|
|
53
|
+
expect(document?.content).toMatch(/<noscript><img[\s\S]*<template/)
|
|
54
|
+
|
|
55
|
+
const app = findFile(output, 'pages', '_app')
|
|
56
|
+
expect(app?.content).toContain("import TqBodyCode from '../components/tq-body-code'")
|
|
57
|
+
expect(app?.content).toContain('<TqBodyCode />')
|
|
58
|
+
|
|
59
|
+
const component = findFile(output, 'components', 'tq-body-code')
|
|
60
|
+
expect(component?.content).toContain('useEffect')
|
|
61
|
+
expect(component?.content).toContain(`template[${BODY_CODE_TEMPLATE_ATTR}]`)
|
|
62
|
+
expect(component?.content).toContain('script.async = false')
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('adds nothing when the project has no body custom code', async () => {
|
|
66
|
+
const output = await generator.generateProject(buildUidlWithBodyCode(undefined), template)
|
|
67
|
+
expect(findFile(output, 'pages', '_app')?.content).not.toContain('TqBodyCode')
|
|
68
|
+
expect(findFile(output, 'components', 'tq-body-code')).toBeUndefined()
|
|
69
|
+
})
|
|
70
|
+
})
|
|
71
|
+
})
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { resolve } from 'path'
|
|
2
|
+
import { BODY_CODE_UNPACK_SCRIPT, wrapBodyCustomCode } from '../src/body-code/body-code-component'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The unpacker run against a DOM: what it does to each kind of script in the
|
|
6
|
+
* project's body custom code. This repo's own jsdom 16 cannot load (see
|
|
7
|
+
* teleport-project-generator-html/__tests__/state-runtime/runtime.ts), so the
|
|
8
|
+
* editor checkout next to this one lends its jsdom.
|
|
9
|
+
*
|
|
10
|
+
* What a DOM cannot show is why an inline script names the document: Chromium
|
|
11
|
+
* reports no file for an error thrown by a script inserted after the page was
|
|
12
|
+
* parsed, and _document's inline script error guard knows the project's own
|
|
13
|
+
* scripts by the document's address. Measured over http with the built
|
|
14
|
+
* packages: without the name such an error reaches the app's own error
|
|
15
|
+
* listener in Chromium, with it the guard stops it (WebKit names the document
|
|
16
|
+
* either way).
|
|
17
|
+
*/
|
|
18
|
+
const loadJSDOM = () => {
|
|
19
|
+
const candidates = ['jsdom', resolve(__dirname, '../../../../teleport-gui/node_modules/jsdom')]
|
|
20
|
+
for (const candidate of candidates) {
|
|
21
|
+
try {
|
|
22
|
+
// tslint:disable-next-line no-var-requires
|
|
23
|
+
return require(candidate).JSDOM
|
|
24
|
+
} catch (error) {
|
|
25
|
+
// the next candidate
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return undefined
|
|
29
|
+
}
|
|
30
|
+
const JSDOM = loadJSDOM()
|
|
31
|
+
if (!JSDOM) {
|
|
32
|
+
// tslint:disable-next-line no-console
|
|
33
|
+
console.warn('body code unpack tests skipped: no jsdom loads (see the note in this file)')
|
|
34
|
+
}
|
|
35
|
+
const describeInDom = JSDOM ? describe : describe.skip
|
|
36
|
+
|
|
37
|
+
const PAGE = 'https://site.test/pricing'
|
|
38
|
+
const NAMED_AFTER_THE_PAGE = `\n//# sourceURL=${PAGE}`
|
|
39
|
+
|
|
40
|
+
const STRUCTURED_DATA = '{"@context":"https://schema.org","@type":"Organization","name":"Holm"}'
|
|
41
|
+
|
|
42
|
+
const BODY_CODE = [
|
|
43
|
+
'<script src="https://cdn.site.test/widget.js"></script>',
|
|
44
|
+
'<script>window.__order = (window.__order || []).concat("helper")</script>',
|
|
45
|
+
'<script type="module">window.__moduleWasWritten = true</script>',
|
|
46
|
+
`<script type="application/ld+json">${STRUCTURED_DATA}</script>`,
|
|
47
|
+
'<script type="text/template"><p>{{ name }}</p></script>',
|
|
48
|
+
'<script type="text/javascript">window.__order = window.__order.concat("widget setup")</script>',
|
|
49
|
+
'<div id="widget-root"></div>',
|
|
50
|
+
].join('\n')
|
|
51
|
+
|
|
52
|
+
/** The page a visitor is on, with the body custom code shipped inert, then unpacked as TqBodyCode does. */
|
|
53
|
+
const unpackOn = (address: string) => {
|
|
54
|
+
const dom = new JSDOM(
|
|
55
|
+
`<!doctype html><html><body><main>Page</main>${wrapBodyCustomCode(BODY_CODE)}</body></html>`,
|
|
56
|
+
{ url: address, runScripts: 'dangerously' }
|
|
57
|
+
)
|
|
58
|
+
dom.window.eval(BODY_CODE_UNPACK_SCRIPT)
|
|
59
|
+
const scripts = Array.from(dom.window.document.querySelectorAll('script')) as HTMLScriptElement[]
|
|
60
|
+
return { window: dom.window, scripts }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
describeInDom('body custom code, unpacked after hydration', () => {
|
|
64
|
+
it('runs the inline scripts in the order they were written', () => {
|
|
65
|
+
const { window } = unpackOn(PAGE)
|
|
66
|
+
|
|
67
|
+
expect(window.__order).toEqual(['helper', 'widget setup'])
|
|
68
|
+
expect(window.document.querySelector('template')).toBeNull()
|
|
69
|
+
expect(window.document.querySelector('#widget-root')).not.toBeNull()
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('names the document as the source of every inline script that runs', () => {
|
|
73
|
+
const { scripts } = unpackOn(PAGE)
|
|
74
|
+
const [, helper, moduleScript, , , setup] = scripts
|
|
75
|
+
|
|
76
|
+
expect(helper.textContent).toBe(
|
|
77
|
+
`window.__order = (window.__order || []).concat("helper")${NAMED_AFTER_THE_PAGE}`
|
|
78
|
+
)
|
|
79
|
+
expect(moduleScript.textContent).toBe(`window.__moduleWasWritten = true${NAMED_AFTER_THE_PAGE}`)
|
|
80
|
+
expect(setup.textContent).toBe(
|
|
81
|
+
`window.__order = window.__order.concat("widget setup")${NAMED_AFTER_THE_PAGE}`
|
|
82
|
+
)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('names the page without the part after #, as an error names it', () => {
|
|
86
|
+
const { scripts } = unpackOn(`${PAGE}#plans`)
|
|
87
|
+
|
|
88
|
+
expect(scripts[1].textContent?.endsWith(NAMED_AFTER_THE_PAGE)).toBe(true)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('leaves an external script and the data blocks exactly as written', () => {
|
|
92
|
+
const { scripts } = unpackOn(PAGE)
|
|
93
|
+
const [external, , , structuredData, markupTemplate] = scripts
|
|
94
|
+
|
|
95
|
+
expect(external.getAttribute('src')).toBe('https://cdn.site.test/widget.js')
|
|
96
|
+
expect(external.textContent).toBe('')
|
|
97
|
+
expect(structuredData.textContent).toBe(STRUCTURED_DATA)
|
|
98
|
+
expect(JSON.parse(structuredData.textContent || '')).toMatchObject({ name: 'Holm' })
|
|
99
|
+
expect(markupTemplate.textContent).toBe('<p>{{ name }}</p>')
|
|
100
|
+
})
|
|
101
|
+
})
|
|
@@ -0,0 +1,37 @@
|
|
|
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
|
+
const template = JSON.parse(JSON.stringify(NextTemplate)) as GeneratedFolder
|
|
7
|
+
|
|
8
|
+
/** Slider arrows and bullets are styled through Swiper's CSS variables written as element styles. */
|
|
9
|
+
describe('custom-property element styles', () => {
|
|
10
|
+
it('ships --swiper-* variables as written', async () => {
|
|
11
|
+
const uidl = JSON.parse(JSON.stringify(uidlSample)) as ProjectUIDL
|
|
12
|
+
const indexPage = (uidl.root.node.content.children || []).find(
|
|
13
|
+
(child) =>
|
|
14
|
+
child.type === 'conditional' && (child.content as { value?: string }).value === 'index'
|
|
15
|
+
)
|
|
16
|
+
const pageElement = (indexPage as { content: { node: { content: { children: unknown[] } } } })
|
|
17
|
+
.content.node.content
|
|
18
|
+
pageElement.children.push({
|
|
19
|
+
type: 'element',
|
|
20
|
+
content: {
|
|
21
|
+
elementType: 'container',
|
|
22
|
+
attrs: { 'data-thq': { type: 'static', content: 'slider-button-next' } },
|
|
23
|
+
style: {
|
|
24
|
+
'--swiper-navigation-color': { type: 'static', content: '#ff0000' },
|
|
25
|
+
'--swiper-navigation-size': { type: 'static', content: '24px' },
|
|
26
|
+
},
|
|
27
|
+
children: [],
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
const output = await createNextProjectGenerator().generateProject(uidl, template)
|
|
31
|
+
const page = output.subFolders
|
|
32
|
+
.find((s) => s.name === 'pages')
|
|
33
|
+
?.files.find((f) => f.name === 'index')
|
|
34
|
+
expect(page?.content).toContain('--swiper-navigation-color: #ff0000')
|
|
35
|
+
expect(page?.content).toContain('--swiper-navigation-size: 24px')
|
|
36
|
+
})
|
|
37
|
+
})
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import generate from '@babel/generator'
|
|
2
|
+
import { ChunkDefinition, ProjectUIDL } from '@teleporthq/teleport-types'
|
|
3
|
+
import { InlineScriptErrorGuard } from '@teleporthq/teleport-shared'
|
|
4
|
+
import { createDocumentFileChunks } from '../src/utils'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A script in the project's custom code that throws used to cover every page of
|
|
8
|
+
* the generated app with Next's "Unhandled Runtime Error" overlay. `_document`
|
|
9
|
+
* now runs the inline script error guard before anything else in <head>, and
|
|
10
|
+
* only for a project that has scripts of its own.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const uidlWith = (globals: Record<string, unknown>): ProjectUIDL =>
|
|
14
|
+
({
|
|
15
|
+
name: 'site',
|
|
16
|
+
globals: {
|
|
17
|
+
settings: { title: 'Site', language: 'en' },
|
|
18
|
+
meta: [{ name: 'description', content: 'Site' }],
|
|
19
|
+
assets: [{ type: 'style', path: 'https://fonts.test/font.css' }],
|
|
20
|
+
...globals,
|
|
21
|
+
},
|
|
22
|
+
root: { name: 'App', node: { type: 'element', content: { elementType: 'Router' } } },
|
|
23
|
+
} as unknown as ProjectUIDL)
|
|
24
|
+
|
|
25
|
+
const documentSource = (uidl: ProjectUIDL): string => {
|
|
26
|
+
const chunks = createDocumentFileChunks(uidl, { assets: {} } as never)
|
|
27
|
+
const chunk = (chunks.js as ChunkDefinition[])[0]
|
|
28
|
+
return generate(chunk.content as never).code
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe('generated _document — inline script error guard', () => {
|
|
32
|
+
it('runs before everything else in <head> when the custom code has a script', () => {
|
|
33
|
+
const source = documentSource(
|
|
34
|
+
uidlWith({ customCode: { body: '<script>new Accordion().init()</script>' } })
|
|
35
|
+
)
|
|
36
|
+
const head = source.slice(source.indexOf('<Head>') + '<Head>'.length).trimStart()
|
|
37
|
+
|
|
38
|
+
expect(head.startsWith('<script dangerouslySetInnerHTML={{')).toBe(true)
|
|
39
|
+
expect(source).toContain(JSON.stringify(InlineScriptErrorGuard.INLINE_SCRIPT_ERROR_GUARD))
|
|
40
|
+
expect(source.indexOf('stopImmediatePropagation')).toBeLessThan(
|
|
41
|
+
source.indexOf('new Accordion().init()')
|
|
42
|
+
)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('runs for an inline script asset too', () => {
|
|
46
|
+
const source = documentSource(
|
|
47
|
+
uidlWith({ assets: [{ type: 'script', content: 'console.log(1)' }] })
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
expect(source).toContain('stopImmediatePropagation')
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('leaves the document of a project without scripts of its own untouched', () => {
|
|
54
|
+
const source = documentSource(uidlWith({ customCode: { head: '<style>body{}</style>' } }))
|
|
55
|
+
|
|
56
|
+
expect(source).not.toContain('stopImmediatePropagation')
|
|
57
|
+
})
|
|
58
|
+
})
|
|
@@ -26,6 +26,9 @@ import { generateQrCodeComponentCode } from '../src/widgets/qrcode-component'
|
|
|
26
26
|
import { generateEmojiPickerComponentCode } from '../src/widgets/emoji-picker-component'
|
|
27
27
|
import { generateColorPickerComponentCode } from '../src/widgets/color-picker-component'
|
|
28
28
|
import { generateFormFileInputComponentCode } from '../src/widgets/form-file-input-component'
|
|
29
|
+
import { generateScrollSceneComponentCode } from '../src/widgets/scroll-scene-component'
|
|
30
|
+
import { generateScrollVideoComponentCode } from '../src/widgets/scroll-video-component'
|
|
31
|
+
import { generatePageTransitionComponentCode } from '../src/page-transition/page-transition-component'
|
|
29
32
|
import { COLLAPSIBLE_TEXT_OVERFLOW_COMPONENT_SOURCE } from '../src/collapsible-text/collapsible-text-overflow-component'
|
|
30
33
|
import { NAV_ACTIVE_LINK_COMPONENT_SOURCE } from '../src/nav-active-link/nav-active-link-component'
|
|
31
34
|
import { generateRichContentEmbedsComponentCode } from '../src/rich-content-embeds/embed-activator-component'
|
|
@@ -39,6 +42,12 @@ const EMITTED_COMPONENTS: Array<[string, string]> = [
|
|
|
39
42
|
['rich-text-editor (with embeds)', generateRichTextEditorComponentCode({ withEmbeds: true })],
|
|
40
43
|
['rich-content-embeds', generateRichContentEmbedsComponentCode()],
|
|
41
44
|
['tq-motion', generateMotionComponentCode()],
|
|
45
|
+
['tq-scroll-scene', generateScrollSceneComponentCode()],
|
|
46
|
+
['tq-scroll-video', generateScrollVideoComponentCode()],
|
|
47
|
+
[
|
|
48
|
+
'tq-page-transition',
|
|
49
|
+
generatePageTransitionComponentCode({ preset: 'slide-up', duration: 0.35, easing: 'ease-out' }),
|
|
50
|
+
],
|
|
42
51
|
['tq-signature', generateSignatureComponentCode()],
|
|
43
52
|
['tq-categories-filter', generateCategoriesFilterComponentCode()],
|
|
44
53
|
['tq-categories-megamenu', generateCategoriesMegamenuComponentCode()],
|
|
@@ -63,7 +72,7 @@ describe('emitted React components obey the Rules of Hooks', () => {
|
|
|
63
72
|
|
|
64
73
|
it('covers every component source the generator can emit', () => {
|
|
65
74
|
// A new widget added without a row here would ship unguarded.
|
|
66
|
-
expect(EMITTED_COMPONENTS).toHaveLength(
|
|
75
|
+
expect(EMITTED_COMPONENTS).toHaveLength(21)
|
|
67
76
|
})
|
|
68
77
|
})
|
|
69
78
|
|
|
@@ -79,4 +79,74 @@ describe('Next generator with a Motion element', () => {
|
|
|
79
79
|
const npmrc = outputFolder.files.find((file) => file.name === '.npmrc')
|
|
80
80
|
expect(npmrc?.content).toContain('legacy-peer-deps=true')
|
|
81
81
|
})
|
|
82
|
+
|
|
83
|
+
it('scroll trigger interpolates the FULL from/to state (canvas parity), with scrub + offset support', async () => {
|
|
84
|
+
const outputFolder = await generator.generateProject(buildUidlWithMotion(), template)
|
|
85
|
+
const component = findFile(outputFolder, 'components', 'tq-motion')
|
|
86
|
+
const code = component?.content || ''
|
|
87
|
+
|
|
88
|
+
// The scroll path writes the whole interpolated state (transform families,
|
|
89
|
+
// opacity, filter) to the element — the historical y-only parallax is gone.
|
|
90
|
+
expect(code).toContain('applyScrollState')
|
|
91
|
+
expect(code).toContain('useMotionValueEvent')
|
|
92
|
+
expect(code).not.toContain('parallaxY')
|
|
93
|
+
expect(code).toMatch(/TRANSFORM_KEYS = \['x', 'y', 'scale', 'rotate'\]/)
|
|
94
|
+
|
|
95
|
+
// Scrub smoothing and the scroll-offset windows are wired.
|
|
96
|
+
expect(code).toContain('useSpring')
|
|
97
|
+
expect(code).toContain('SCROLL_OFFSET_RANGES')
|
|
98
|
+
for (const preset of ['pass', 'contained', 'enter', 'exit']) {
|
|
99
|
+
expect(code).toContain(preset)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Blur interpolation mirrors the canvas runtime's BLUR_RE handling.
|
|
103
|
+
expect(code).toContain('blur(')
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
// A site with a page transition wraps every page in an AnimatePresence with
|
|
107
|
+
// initial={false}, which skips the mount animation of every motion component
|
|
108
|
+
// under it. A row that loops by itself (trigger load, repeat -1) never started
|
|
109
|
+
// on the generated site, and stood parked at its end state (2026-10-03).
|
|
110
|
+
it('starts a load animation from a flag set after mount, so a page transition cannot skip it', async () => {
|
|
111
|
+
const outputFolder = await generator.generateProject(buildUidlWithMotion(), template)
|
|
112
|
+
const component = findFile(outputFolder, 'components', 'tq-motion')
|
|
113
|
+
const code = component?.content || ''
|
|
114
|
+
|
|
115
|
+
expect(code).toContain('animate: played ? toVars : fromVars')
|
|
116
|
+
expect(code).toContain(
|
|
117
|
+
'buildAnimProps(trigger, fromVars, toVars, transition, revealed, played)'
|
|
118
|
+
)
|
|
119
|
+
expect(code).not.toContain(
|
|
120
|
+
"case 'load':\n return { initial: fromVars, animate: toVars, transition }"
|
|
121
|
+
)
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
// Vlad, 2026-10-03: for a visitor who asks for less motion a row that slides
|
|
125
|
+
// by itself stands still and can be swiped, so every item stays reachable.
|
|
126
|
+
it('lets a row that slides by itself be swiped when the visitor asks for less motion', async () => {
|
|
127
|
+
const outputFolder = await generator.generateProject(buildUidlWithMotion(), template)
|
|
128
|
+
const code = findFile(outputFolder, 'components', 'tq-motion')?.content || ''
|
|
129
|
+
|
|
130
|
+
expect(code).toContain('const letMarqueeSwipe = (track) => {')
|
|
131
|
+
expect(code).toContain("frame.style.overflowX = 'auto'")
|
|
132
|
+
expect(code).toContain(
|
|
133
|
+
'React.useEffect(() => {\n if (still) {\n letMarqueeSwipe(ref.current)'
|
|
134
|
+
)
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
// The server cannot know that a visitor asks for less motion and renders the
|
|
138
|
+
// animated element with its starting style. A first render that drew the still
|
|
139
|
+
// element instead left that style in place: every entrance stayed invisible for
|
|
140
|
+
// those visitors (2026-10-03, React keeps server markup it disagrees with).
|
|
141
|
+
it('draws the still element for less motion only once the page is mounted', async () => {
|
|
142
|
+
const outputFolder = await generator.generateProject(buildUidlWithMotion(), template)
|
|
143
|
+
const code = findFile(outputFolder, 'components', 'tq-motion')?.content || ''
|
|
144
|
+
|
|
145
|
+
expect(code).toContain('React.useEffect(() => setMounted(true), [])')
|
|
146
|
+
expect(code).toContain('const still = mounted && Boolean(shouldReduceMotion)')
|
|
147
|
+
expect(code).toContain(
|
|
148
|
+
'if (still) {\n return (\n <div ref={ref} style={wrapperStyle} {...rest}>'
|
|
149
|
+
)
|
|
150
|
+
expect(code).not.toContain('if (shouldReduceMotion) {\n return (')
|
|
151
|
+
})
|
|
82
152
|
})
|
|
@@ -223,7 +223,7 @@ describe('generated TqMotion source', () => {
|
|
|
223
223
|
expect(code).not.toContain('<motion.div key={index}')
|
|
224
224
|
// The outer wrapper stays a motion.div so the stagger and group branches render
|
|
225
225
|
// the same element type (framer's useInView must not lose its node on a flip).
|
|
226
|
-
expect(code).toContain('<motion.div ref={ref} style={
|
|
226
|
+
expect(code).toContain('<motion.div ref={ref} style={wrapperStyle} {...rest}>')
|
|
227
227
|
})
|
|
228
228
|
|
|
229
229
|
it('keeps the framer path for a REPEATING stagger (a CSS transition plays once)', () => {
|
|
@@ -237,14 +237,18 @@ describe('generated TqMotion source', () => {
|
|
|
237
237
|
describe('generated cssFromState (canvas parity)', () => {
|
|
238
238
|
const cssFromState = extractCssFromState()
|
|
239
239
|
|
|
240
|
-
it(
|
|
240
|
+
it("writes the individual transform properties — the author's transform survives (canvas parity)", () => {
|
|
241
241
|
expect(cssFromState({ opacity: 0, y: 30, scale: 0.8 })).toEqual({
|
|
242
242
|
opacity: 0,
|
|
243
|
-
|
|
243
|
+
translate: '0px 30px',
|
|
244
|
+
scale: '0.8',
|
|
244
245
|
})
|
|
245
246
|
expect(cssFromState({ x: -10, y: 4, scale: 2, rotate: -8 })).toEqual({
|
|
246
|
-
|
|
247
|
+
translate: '-10px 4px',
|
|
248
|
+
scale: '2',
|
|
249
|
+
rotate: '-8deg',
|
|
247
250
|
})
|
|
251
|
+
expect(cssFromState({ x: '-25%' })).toEqual({ translate: '-25% 0px' })
|
|
248
252
|
})
|
|
249
253
|
|
|
250
254
|
it('passes non-transform properties through untouched', () => {
|