@teleporthq/teleport-uidl-resolver 0.43.70 → 0.43.73

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.
@@ -42,6 +42,56 @@ describe('insertLink', () => {
42
42
  expect(result.content.attrs.url.content).toBe(link.content.url.content)
43
43
  })
44
44
 
45
+ // Run 10f154ff (Atelier Luzo homepage rail): the page wrote
46
+ // `<div class="surface-media"><a><img></a></div>` and the design system sizes
47
+ // that image through `.surface-media > a > img`. The link came out as
48
+ // `<a><div><img></div></a>`: the extra <div> broke the child chain, the image
49
+ // rendered at its natural 2132 px and blew the rail's card up. The canvas,
50
+ // which does not insert the wrapper, looked right.
51
+ it('a container that is nothing but the link becomes the anchor itself', () => {
52
+ const image = elementNode('image', { src: { type: 'static', content: '/a.jpg' } })
53
+ const bare = elementNode('container', {}, [image])
54
+ bare.content.abilities = { link: urlMockedDefinition() as UIDLURLLinkNode }
55
+ bare.content.attrs = {
56
+ 'data-teleport-link-pending': { type: 'static', content: 'Project Details' },
57
+ }
58
+ const well = elementNode('container', {}, [bare])
59
+
60
+ const result = insertLinks(well, {}, false)
61
+ const anchor = result.content.children[0] as UIDLElementNode
62
+
63
+ expect(anchor.content.elementType).toBe('link')
64
+ expect(anchor.content.attrs.url).toBeDefined()
65
+ expect(anchor.content.attrs['data-teleport-link-pending'].content).toBe('Project Details')
66
+ // The image is the anchor's own child: no element in between.
67
+ expect((anchor.content.children[0] as UIDLElementNode).content.elementType).toBe('image')
68
+ expect(anchor.content.style?.display).toBeUndefined()
69
+ // it says it was a box, which is how the reset stylesheet keeps it a block
70
+ expect(anchor.content.attrs['data-thq-link-box']).toEqual({ type: 'static', content: 'true' })
71
+ })
72
+
73
+ it('still wraps an element that has a box of its own: a style, a class, an event or a runtime attribute', () => {
74
+ const link = urlMockedDefinition() as UIDLURLLinkNode
75
+ const styled = elementNode('container')
76
+ styled.content.style = { padding: { type: 'static', content: '8px' } }
77
+ const classed = elementNode('container')
78
+ classed.content.referencedStyles = {
79
+ card: { type: 'style-map', content: { mapType: 'project-referenced', referenceId: 'card' } },
80
+ } as never
81
+ const bound = elementNode('container')
82
+ bound.content.attrs = { 'data-scroll-bind': { type: 'static', content: '[]' } }
83
+ const clickable = elementNode('container')
84
+ clickable.content.events = { click: [] }
85
+
86
+ for (const node of [styled, classed, bound, clickable]) {
87
+ node.content.abilities = { link }
88
+ const result = insertLinks(elementNode('container', {}, [node]), {}, false)
89
+ const wrapper = result.content.children[0] as UIDLElementNode
90
+ expect(wrapper.content.elementType).toBe('link')
91
+ expect((wrapper.content.children[0] as UIDLElementNode).content.elementType).toBe('container')
92
+ }
93
+ })
94
+
45
95
  it('replaces a child', () => {
46
96
  const node = elementNode('container', {}, [
47
97
  elementNode('container'),
@@ -350,7 +400,7 @@ describe('insertLink', () => {
350
400
  })
351
401
  })
352
402
 
353
- it('marks the link wrapper display:contents when the flex parent uses a project-referenced style', () => {
403
+ it('a classed div that is a flex item becomes the link itself when the parent is flex through a project class', () => {
354
404
  const child = elementNode('container')
355
405
  child.content.abilities = { link: navlinkMockedDefinition() }
356
406
  child.content.referencedStyles = {
@@ -370,13 +420,54 @@ describe('insertLink', () => {
370
420
  }
371
421
 
372
422
  const result = insertLinks(parent, flexProjectStyleSetOptions('flex'), false)
423
+ const tile = result.content.children[0] as UIDLElementNode
424
+
425
+ // no element in between: the tile is the navlink, with its class and no display of the link's making
426
+ expect(tile.content.elementType).toBe('navlink')
427
+ expect(tile.content.attrs.transitionTo).toBeDefined()
428
+ expect(tile.content.referencedStyles?.TQ_tile).toBeDefined()
429
+ expect(tile.content.style?.display).toBeUndefined()
430
+ expect(tile.content.children ?? []).toHaveLength(0)
431
+ })
432
+
433
+ it('keeps scroll-runtime attributes on the styled element, never on the box-less link wrapper', () => {
434
+ // A heading keeps its tag, so it is the case that still gets a wrapper.
435
+ const child = elementNode('text')
436
+ child.content.semanticType = 'h3'
437
+ child.content.abilities = { link: urlMockedDefinition() }
438
+ child.content.attrs = {
439
+ 'data-scroll-bind': {
440
+ type: 'static',
441
+ content: '[{"prop":"opacity","at":[0,1],"values":[0,1]}]',
442
+ },
443
+ 'data-scroll-bind-rel': { type: 'static', content: '[]' },
444
+ 'data-snap-into-view': { type: 'static', content: 'gentle' },
445
+ 'data-chapter-window': { type: 'static', content: '0.5-1' },
446
+ 'data-analytics': { type: 'static', content: 'cta' },
447
+ }
448
+ const parent = elementNode('container', {}, [child])
449
+ parent.content.style = { display: { type: 'static', content: 'flex' } }
450
+
451
+ const result = insertLinks(parent, {}, false)
373
452
  const wrapper = result.content.children[0] as UIDLElementNode
453
+ const styledChild = wrapper.content.children[0] as UIDLElementNode
374
454
 
375
- expect(wrapper.content.elementType).toBe('navlink')
376
455
  expect(wrapper.content.style?.display).toEqual({ type: 'static', content: 'contents' })
377
- // the original styled node stays inside the wrapper, keeping its class
378
- const styledChild = wrapper.content.children[0] as UIDLElementNode
379
- expect(styledChild.content.referencedStyles?.TQ_tile).toBeDefined()
456
+ expect(Object.keys(wrapper.content.attrs)).not.toEqual(
457
+ expect.arrayContaining([
458
+ 'data-scroll-bind',
459
+ 'data-scroll-bind-rel',
460
+ 'data-snap-into-view',
461
+ 'data-chapter-window',
462
+ ])
463
+ )
464
+ expect(styledChild.content.attrs['data-scroll-bind'].content).toContain('opacity')
465
+ expect(styledChild.content.attrs['data-scroll-bind-rel']).toBeDefined()
466
+ expect(styledChild.content.attrs['data-snap-into-view'].content).toBe('gentle')
467
+ expect(styledChild.content.attrs['data-chapter-window'].content).toBe('0.5-1')
468
+ // ordinary data attributes still travel to the anchor
469
+ expect(wrapper.content.attrs['data-analytics'].content).toBe('cta')
470
+ expect(styledChild.content.attrs['data-analytics']).toBeUndefined()
380
471
  })
381
472
 
382
473
  it('does not mark the link wrapper display:contents when the referenced parent is not flex/grid', () => {
@@ -408,6 +499,8 @@ describe('insertLink', () => {
408
499
  stopped matching the editor, which renders no wrapper at all. */
409
500
  const card = elementNode('container')
410
501
  card.content.abilities = { link: navlinkMockedDefinition() }
502
+ // A card has a box of its own; a bare container would become the anchor itself.
503
+ card.content.style = { flex: { type: 'static', content: '0 0 320px' } }
411
504
 
412
505
  const fragment = elementNode('fragment', {}, [card])
413
506
  const grid = elementNode('container', {}, [fragment])
@@ -421,8 +514,23 @@ describe('insertLink', () => {
421
514
 
422
515
  const result = insertLinks(grid, flexProjectStyleSetOptions('flex'), false)
423
516
  const resolvedFragment = result.content.children[0] as UIDLElementNode
424
- const wrapper = resolvedFragment.content.children[0] as UIDLElementNode
425
-
517
+ const linkedCard = resolvedFragment.content.children[0] as UIDLElementNode
518
+
519
+ // The grid was seen through the fragment: the card is its item, so the card
520
+ // itself is the navlink and its sizing never leaves it.
521
+ expect(linkedCard.content.elementType).toBe('navlink')
522
+ expect(linkedCard.content.style?.flex).toEqual({ type: 'static', content: '0 0 320px' })
523
+ expect(linkedCard.content.style?.display).toBeUndefined()
524
+
525
+ // An element that keeps its tag is wrapped, and that wrapper draws no box.
526
+ const heading = elementNode('text')
527
+ heading.content.semanticType = 'h3'
528
+ heading.content.abilities = { link: navlinkMockedDefinition() }
529
+ const headingGrid = elementNode('container', {}, [elementNode('fragment', {}, [heading])])
530
+ headingGrid.content.referencedStyles = grid.content.referencedStyles
531
+ const wrapped = insertLinks(headingGrid, flexProjectStyleSetOptions('flex'), false)
532
+ const wrapper = (wrapped.content.children[0] as UIDLElementNode).content
533
+ .children[0] as UIDLElementNode
426
534
  expect(wrapper.content.elementType).toBe('navlink')
427
535
  expect(wrapper.content.style?.display).toEqual({ type: 'static', content: 'contents' })
428
536
  })
@@ -451,6 +559,274 @@ describe('insertLink', () => {
451
559
  })
452
560
  })
453
561
 
562
+ /* The link rules generated sites already depend on. Each of them was a fix for a
563
+ broken page at some point (flex items collapsing behind the wrapper in 2020,
564
+ a linked root node in 2024, ids and data attributes lost in 2025), so a change
565
+ to how links are emitted has to keep every one of them. */
566
+ describe('link rules that generated sites depend on', () => {
567
+ const linked = (node: UIDLElementNode): UIDLElementNode => {
568
+ node.content.abilities = { link: urlMockedDefinition() as UIDLURLLinkNode }
569
+ return node
570
+ }
571
+ const styledCard = (): UIDLElementNode => {
572
+ const card = elementNode('container')
573
+ card.content.style = { padding: { type: 'static', content: '24px' } }
574
+ return linked(card)
575
+ }
576
+ const withDisplay = (display: string, child: UIDLElementNode): UIDLElementNode => {
577
+ const parent = elementNode('container', {}, [child])
578
+ parent.content.style = { display: { type: 'static', content: display } }
579
+ return parent
580
+ }
581
+
582
+ const linkedHeading = (): UIDLElementNode => {
583
+ const heading = elementNode('text')
584
+ heading.content.semanticType = 'h3'
585
+ heading.content.style = { margin: { type: 'static', content: '0' } }
586
+ return linked(heading)
587
+ }
588
+
589
+ it.each(['flex', 'inline-flex', 'grid', 'inline-grid'])(
590
+ 'a plain div that is an item of a %s parent becomes the link itself: a box-less wrapper cannot take keyboard focus',
591
+ (display) => {
592
+ const result = insertLinks(withDisplay(display, styledCard()), {}, false)
593
+ const card = result.content.children[0] as UIDLElementNode
594
+
595
+ expect(card.content.elementType).toBe('link')
596
+ expect(card.content.attrs.url).toBeDefined()
597
+ expect(card.content.style?.padding).toEqual({ type: 'static', content: '24px' })
598
+ // no display, no wrapper marker, no element in between: only the word that it was a box
599
+ expect(card.content.style?.display).toBeUndefined()
600
+ expect(card.content.attrs['data-thq-link-wrapper']).toBeUndefined()
601
+ expect(card.content.attrs['data-thq-link-box']).toEqual({ type: 'static', content: 'true' })
602
+ expect(card.content.children ?? []).toHaveLength(0)
603
+ }
604
+ )
605
+
606
+ // Measured on 0.43.71 (release audit, 2026-10-05): a menu whose row turns into
607
+ // a plain block on phones kept its links side by side there, and three bare
608
+ // linked boxes in a block parent sat on one line. The canvas draws <div>s.
609
+ it('a box that becomes the link is marked whatever its parent, so the reset keeps it a block', () => {
610
+ const bare = linked(elementNode('container', {}, [elementNode('text')]))
611
+ const menuItem = styledCard()
612
+
613
+ const inBlockParent = insertLinks(withDisplay('block', bare), {}, false).content
614
+ .children[0] as UIDLElementNode
615
+ const inRow = insertLinks(withDisplay('flex', menuItem), {}, false).content
616
+ .children[0] as UIDLElementNode
617
+
618
+ for (const anchor of [inBlockParent, inRow]) {
619
+ expect(anchor.content.elementType).toBe('link')
620
+ expect(anchor.content.attrs['data-thq-link-box']).toEqual({
621
+ type: 'static',
622
+ content: 'true',
623
+ })
624
+ }
625
+ })
626
+
627
+ it('a wrapper, and an element that is not a box, never say they are one', () => {
628
+ const wrapped = insertLinks(withDisplay('block', styledCard()), {}, false).content
629
+ .children[0] as UIDLElementNode
630
+ const heading = insertLinks(withDisplay('flex', linkedHeading()), {}, false).content
631
+ .children[0] as UIDLElementNode
632
+ const button = insertLinks(linked(elementNode('button')), {}, false)
633
+ const span = elementNode('text')
634
+ span.content.semanticType = 'span'
635
+ const textLink = insertLinks(linked(span), {}, false)
636
+
637
+ for (const anchor of [wrapped, heading, button, textLink]) {
638
+ expect(anchor.content.elementType).toBe('link')
639
+ expect(anchor.content.attrs['data-thq-link-box']).toBeUndefined()
640
+ }
641
+ })
642
+
643
+ it.each(['flex', 'inline-flex', 'grid', 'inline-grid'])(
644
+ 'an element that keeps its tag is wrapped inside a %s parent, and that wrapper draws no box so the element stays the item',
645
+ (display) => {
646
+ const result = insertLinks(withDisplay(display, linkedHeading()), {}, false)
647
+ const wrapper = result.content.children[0] as UIDLElementNode
648
+
649
+ expect(wrapper.content.elementType).toBe('link')
650
+ expect(wrapper.content.style?.display).toEqual({ type: 'static', content: 'contents' })
651
+ // it says so, which is how the reset stylesheet finds it to paint a focus ring on its child
652
+ expect(wrapper.content.attrs['data-thq-link-wrapper']).toEqual({
653
+ type: 'static',
654
+ content: 'true',
655
+ })
656
+ const heading = wrapper.content.children[0] as UIDLElementNode
657
+ expect(heading.content.semanticType).toBe('h3')
658
+ expect(heading.content.style?.margin).toEqual({ type: 'static', content: '0' })
659
+ }
660
+ )
661
+
662
+ it('a div that becomes the link keeps its id, its data attributes and its scene lanes on itself', () => {
663
+ const card = styledCard()
664
+ card.content.attrs = {
665
+ id: { type: 'static', content: 'featured-card' },
666
+ 'data-analytics': { type: 'static', content: 'card' },
667
+ 'data-scroll-bind': { type: 'static', content: 'rise-in' },
668
+ }
669
+
670
+ const anchor = insertLinks(withDisplay('grid', card), {}, false).content
671
+ .children[0] as UIDLElementNode
672
+
673
+ expect(anchor.content.elementType).toBe('link')
674
+ for (const kept of ['id', 'data-analytics', 'data-scroll-bind', 'url']) {
675
+ expect(anchor.content.attrs[kept]).toBeDefined()
676
+ }
677
+ })
678
+
679
+ it('a div stays wrapped when it listens to events, carries an attribute an anchor may not, or its parent is not known', () => {
680
+ const clickable = styledCard()
681
+ clickable.content.events = { click: [] }
682
+ const named = styledCard()
683
+ named.content.attrs = { name: { type: 'static', content: 'card' } }
684
+
685
+ for (const node of [clickable, named]) {
686
+ const wrapper = insertLinks(withDisplay('flex', node), {}, false).content
687
+ .children[0] as UIDLElementNode
688
+ expect(wrapper.content.elementType).toBe('link')
689
+ expect((wrapper.content.children[0] as UIDLElementNode).content.elementType).toBe('container')
690
+ }
691
+ const rootWrapper = insertLinks(styledCard(), {}, false)
692
+ expect((rootWrapper.content.children[0] as UIDLElementNode).content.elementType).toBe(
693
+ 'container'
694
+ )
695
+ })
696
+
697
+ it('the wrapper keeps its own box inside a block parent', () => {
698
+ const result = insertLinks(withDisplay('block', styledCard()), {}, false)
699
+ const wrapper = result.content.children[0] as UIDLElementNode
700
+
701
+ expect(wrapper.content.elementType).toBe('link')
702
+ expect(wrapper.content.style?.display).toBeUndefined()
703
+ // a wrapper with a box draws its own focus ring and is not marked
704
+ expect(wrapper.content.attrs['data-thq-link-wrapper']).toBeUndefined()
705
+ })
706
+
707
+ it('a linked root node is wrapped without a box: its parent is unknown here', () => {
708
+ const wrapper = insertLinks(styledCard(), {}, false)
709
+
710
+ expect(wrapper.content.elementType).toBe('link')
711
+ expect(wrapper.content.style?.display).toEqual({ type: 'static', content: 'contents' })
712
+ })
713
+
714
+ it('moves the attributes an anchor may carry onto the wrapper and leaves the rest on the element', () => {
715
+ const card = styledCard()
716
+ card.content.attrs = {
717
+ id: { type: 'static', content: 'featured-card' },
718
+ class: { type: 'static', content: 'promo' },
719
+ title: { type: 'static', content: 'Featured' },
720
+ 'data-analytics': { type: 'static', content: 'card' },
721
+ 'aria-current': { type: 'static', content: 'page' },
722
+ role: { type: 'static', content: 'group' },
723
+ name: { type: 'static', content: 'card' },
724
+ }
725
+
726
+ const wrapper = insertLinks(withDisplay('block', card), {}, false).content
727
+ .children[0] as UIDLElementNode
728
+ const element = wrapper.content.children[0] as UIDLElementNode
729
+
730
+ for (const moved of ['id', 'class', 'title', 'data-analytics', 'aria-current']) {
731
+ expect(wrapper.content.attrs[moved]).toBeDefined()
732
+ expect(element.content.attrs[moved]).toBeUndefined()
733
+ }
734
+ for (const kept of ['role', 'name']) {
735
+ expect(wrapper.content.attrs[kept]).toBeUndefined()
736
+ expect(element.content.attrs[kept]).toBeDefined()
737
+ }
738
+ })
739
+
740
+ it('never turns an element with a meaning of its own into the anchor: it is wrapped and keeps its tag', () => {
741
+ const heading = elementNode('text')
742
+ heading.content.semanticType = 'h2'
743
+ const paragraph = elementNode('text')
744
+ paragraph.content.semanticType = 'p'
745
+ const image = elementNode('image', { src: { type: 'static', content: '/a.jpg' } })
746
+ const listItem = elementNode('container')
747
+ listItem.content.semanticType = 'li'
748
+ const section = elementNode('container')
749
+ section.content.semanticType = 'section'
750
+
751
+ for (const node of [heading, paragraph, image, listItem, section]) {
752
+ const { elementType, semanticType } = node.content
753
+ const wrapper = insertLinks(withDisplay('block', linked(node)), {}, false).content
754
+ .children[0] as UIDLElementNode
755
+ const element = wrapper.content.children[0] as UIDLElementNode
756
+
757
+ expect(wrapper.content.elementType).toBe('link')
758
+ expect(element.content.elementType).toBe(elementType)
759
+ expect(element.content.semanticType).toBe(semanticType)
760
+ }
761
+ })
762
+
763
+ it('a button and a span become the anchor themselves and keep their own styles', () => {
764
+ const button = elementNode('button')
765
+ button.content.style = { padding: { type: 'static', content: '12px' } }
766
+ button.content.attrs = { type: { type: 'static', content: 'button' } }
767
+ const span = elementNode('text')
768
+ span.content.semanticType = 'span'
769
+ span.content.style = { color: { type: 'static', content: 'red' } }
770
+
771
+ const asAnchor = insertLinks(linked(button), {}, false)
772
+ expect(asAnchor.content.elementType).toBe('link')
773
+ expect(asAnchor.content.children ?? []).toHaveLength(0)
774
+ expect(asAnchor.content.style?.padding).toEqual({ type: 'static', content: '12px' })
775
+ expect(asAnchor.content.style?.textAlign).toEqual({ type: 'static', content: 'center' })
776
+ expect(asAnchor.content.attrs.type).toBeUndefined()
777
+ expect(asAnchor.content.attrs.url).toBeDefined()
778
+
779
+ const textAnchor = insertLinks(linked(span), {}, false)
780
+ expect(textAnchor.content.elementType).toBe('link')
781
+ expect(textAnchor.content.style?.color).toEqual({ type: 'static', content: 'red' })
782
+ expect(textAnchor.content.style?.display).toBeUndefined()
783
+ })
784
+
785
+ it('keeps what a motion runtime reads on the element that draws the box, never on the wrapper', () => {
786
+ const card = linkedHeading()
787
+ card.content.attrs = {
788
+ 'data-scene-backdrop': { type: 'static', content: 'true' },
789
+ 'data-scene-track': { type: 'static', content: 'true' },
790
+ 'data-motion-preset': { type: 'static', content: 'slide-up' },
791
+ 'data-tq-motion': { type: 'static', content: 'true' },
792
+ 'data-scroll-video': { type: 'static', content: 'true' },
793
+ 'data-analytics': { type: 'static', content: 'card' },
794
+ }
795
+
796
+ const wrapper = insertLinks(withDisplay('flex', card), {}, false).content
797
+ .children[0] as UIDLElementNode
798
+ const element = wrapper.content.children[0] as UIDLElementNode
799
+
800
+ for (const kept of [
801
+ 'data-scene-backdrop',
802
+ 'data-scene-track',
803
+ 'data-motion-preset',
804
+ 'data-tq-motion',
805
+ 'data-scroll-video',
806
+ ]) {
807
+ expect(element.content.attrs[kept]).toBeDefined()
808
+ expect(wrapper.content.attrs[kept]).toBeUndefined()
809
+ }
810
+ expect(wrapper.content.attrs['data-analytics']).toBeDefined()
811
+ })
812
+
813
+ it('leaves a link that sits inside another link alone: anchors cannot nest', () => {
814
+ const warn = jest.spyOn(console, 'warn').mockImplementation(() => undefined)
815
+ const inner = styledCard()
816
+ const outer = linked(elementNode('container', {}, [inner]))
817
+ outer.content.style = { padding: { type: 'static', content: '8px' } }
818
+
819
+ const wrapper = insertLinks(outer, {}, false)
820
+ const outerElement = wrapper.content.children[0] as UIDLElementNode
821
+ const innerElement = outerElement.content.children[0] as UIDLElementNode
822
+
823
+ expect(wrapper.content.elementType).toBe('link')
824
+ expect(innerElement.content.elementType).toBe('container')
825
+ expect(warn).toHaveBeenCalled()
826
+ warn.mockRestore()
827
+ })
828
+ })
829
+
454
830
  describe('insertLink with link-type prop', () => {
455
831
  it('wraps a container element with a link when link ability references a link-type prop', () => {
456
832
  const node = elementNode('container')
@@ -34,6 +34,21 @@ describe('resolveElement', () => {
34
34
  expect(resolvedElement.attrs.dummy.content).toBe('remains here')
35
35
  })
36
36
 
37
+ it('drops editor-only chapter bookkeeping but keeps the runtime lanes', () => {
38
+ const chapter = element('container', {
39
+ 'data-scroll-bind': staticNode('[{"prop":"opacity","at":[0,1],"values":[0,1]}]'),
40
+ 'data-scroll-bind-rel': staticNode('[{"prop":"opacity","at":[0,1],"values":[0,1]}]'),
41
+ 'data-chapter-window': staticNode('0-0.5'),
42
+ 'data-chapter-template': staticNode('finale'),
43
+ 'data-snap-into-view': staticNode('gentle'),
44
+ })
45
+ const resolver = new Resolver()
46
+ resolver.addMapping(mapping)
47
+ const resolved = resolver.resolveElement(chapter)
48
+
49
+ expect(Object.keys(resolved.attrs)).toEqual(['data-scroll-bind', 'data-snap-into-view'])
50
+ })
51
+
37
52
  it('maps a seflClosing tag', () => {
38
53
  const resolver = new Resolver()
39
54
  const imageElement = element('image', {
@@ -10,6 +10,7 @@ import {
10
10
  import {
11
11
  generateUniqueKeys,
12
12
  createNodesLookup,
13
+ reserveProjectStyleNames,
13
14
  resolveChildren,
14
15
  ensureDataSourceUniqueness,
15
16
  mergeMappings,
@@ -29,6 +30,55 @@ import {
29
30
  } from '@teleporthq/teleport-types'
30
31
  import mapping from './mapping.json'
31
32
 
33
+ // A page named "Manifesto" names its root element `manifesto-container`. The
34
+ // project style sheet is global, and another page had authored a
35
+ // `.manifesto-container` two-column grid: the whole Manifesto page became that
36
+ // grid in the generated site (navigation in one column, hero squeezed into the
37
+ // other), while the editor canvas, which does not use generated names, was fine.
38
+ describe('reserveProjectStyleNames', () => {
39
+ const styleSet = {
40
+ 'manifesto-container': { type: 'reusable-project-style-map', content: {} },
41
+ heroTitle: { type: 'reusable-project-style-map', className: 'hero-title', content: {} },
42
+ } as never
43
+
44
+ it('keeps a generated element class off a class of the project style sheet', () => {
45
+ const root = elementNode('container', {}, [elementNode('text')])
46
+ const uidl = component('Manifesto', root)
47
+ const lookup = {}
48
+
49
+ reserveProjectStyleNames(styleSet, lookup)
50
+ createNodesLookup(uidl, lookup)
51
+ generateUniqueKeys(uidl, lookup)
52
+
53
+ expect(root.content.key).toBe('manifesto-container1')
54
+ expect((root.content.children?.[0].content as UIDLElement).key).toBe('manifesto-text')
55
+ })
56
+
57
+ it('reserves the class a style is emitted under, not only its id', () => {
58
+ const title = elementNode('title')
59
+ const uidl = component('Hero', title)
60
+ const lookup = {}
61
+
62
+ reserveProjectStyleNames(styleSet, lookup)
63
+ createNodesLookup(uidl, lookup)
64
+ generateUniqueKeys(uidl, lookup)
65
+
66
+ expect(title.content.key).toBe('hero-title1')
67
+ })
68
+
69
+ it('changes nothing without a project style sheet', () => {
70
+ const root = elementNode('container')
71
+ const uidl = component('Manifesto', root)
72
+ const lookup = {}
73
+
74
+ reserveProjectStyleNames(undefined, lookup)
75
+ createNodesLookup(uidl, lookup)
76
+ generateUniqueKeys(uidl, lookup)
77
+
78
+ expect(root.content.key).toBe('manifesto-container')
79
+ })
80
+ })
81
+
32
82
  describe('generateUniqueKeys', () => {
33
83
  it('adds name and key to node', async () => {
34
84
  const simpleNode = elementNode('container')
@@ -1 +1 @@
1
- {"version":3,"file":"resolver.d.ts","sourceRoot":"","sources":["../../src/resolver.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,aAAa,EACb,WAAW,EACX,OAAO,EACP,gBAAgB,EAEjB,MAAM,4BAA4B,CAAA;AAQnC;;;GAGG;AACH,MAAM,CAAC,OAAO,OAAO,QAAQ;IAC3B,OAAO,CAAC,OAAO,CAMd;gBAEW,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,EAAE;IAQlC,UAAU,CAAC,OAAO,EAAE,OAAO;IAI3B,WAAW,CAAC,KAAK,EAAE,aAAa,EAAE,OAAO,GAAE,gBAA6C;IAkDxF,cAAc,CACnB,OAAO,EAAE,WAAW,EACpB,OAAO,GAAE,gBAA6C;CAYzD"}
1
+ {"version":3,"file":"resolver.d.ts","sourceRoot":"","sources":["../../src/resolver.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,aAAa,EACb,WAAW,EACX,OAAO,EACP,gBAAgB,EAEjB,MAAM,4BAA4B,CAAA;AAQnC;;;GAGG;AACH,MAAM,CAAC,OAAO,OAAO,QAAQ;IAC3B,OAAO,CAAC,OAAO,CAMd;gBAEW,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,EAAE;IAQlC,UAAU,CAAC,OAAO,EAAE,OAAO;IAI3B,WAAW,CAAC,KAAK,EAAE,aAAa,EAAE,OAAO,GAAE,gBAA6C;IAmDxF,cAAc,CACnB,OAAO,EAAE,WAAW,EACpB,OAAO,GAAE,gBAA6C;CAYzD"}
@@ -67,6 +67,7 @@ var Resolver = /** @class */ (function () {
67
67
  this.mapping = utils.mergeMappings(this.mapping, mapping);
68
68
  };
69
69
  Resolver.prototype.resolveUIDL = function (input, options) {
70
+ var _a;
70
71
  if (options === void 0) { options = { extractedResources: {} }; }
71
72
  var mapping = utils.mergeMappings(this.mapping, options.mapping);
72
73
  var newOptions = __assign(__assign({}, options), { mapping: mapping });
@@ -95,6 +96,7 @@ var Resolver = /** @class */ (function () {
95
96
  // never generated). Left untouched they compile to `ReferenceError`s that
96
97
  // fail the static export / `next build`.
97
98
  (0, unbound_expressions_1.resolveUnboundExpressions)(uidl);
99
+ utils.reserveProjectStyleNames((_a = newOptions.projectStyleSet) === null || _a === void 0 ? void 0 : _a.styleSetDefinitions, lookup);
98
100
  utils.createNodesLookup(uidl, lookup);
99
101
  utils.generateUniqueKeys(uidl, lookup);
100
102
  utils.ensureDataSourceUniqueness(uidl.node);
@@ -1 +1 @@
1
- {"version":3,"file":"resolver.js","sourceRoot":"","sources":["../../src/resolver.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"resolver.js","sourceRoot":"","sources":["../../src/resolver.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,7 @@
1
1
  import { GeneratorOptions, UIDLLinkNode, UIDLElementNode, UIDLPropDefinition } from '@teleporthq/teleport-types';
2
+ export declare const isRuntimeDataAttribute: (attrName: string) => boolean;
3
+ export declare const BOXLESS_LINK_WRAPPER_ATTR = "data-thq-link-wrapper";
4
+ export declare const LINKED_BOX_ATTR = "data-thq-link-box";
2
5
  export declare const insertLinks: (node: UIDLElementNode, options: GeneratorOptions, linkInParent?: boolean, parentNode?: UIDLElementNode, propDefinitions?: Record<string, UIDLPropDefinition>) => UIDLElementNode;
3
6
  export declare const createLinkNode: (link: UIDLLinkNode, options: GeneratorOptions) => UIDLElementNode;
4
7
  //# sourceMappingURL=utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../../src/resolvers/abilities/utils.ts"],"names":[],"mappings":"AACA,OAAO,EACL,gBAAgB,EAChB,YAAY,EAEZ,eAAe,EAEf,kBAAkB,EAInB,MAAM,4BAA4B,CAAA;AAiFnC,eAAO,MAAM,WAAW,SAChB,eAAe,WACZ,gBAAgB,iBACX,OAAO,eACR,eAAe,oBACV,OAAO,MAAM,EAAE,kBAAkB,CAAC,KACnD,eAqTF,CAAA;AA6FD,eAAO,MAAM,cAAc,SAAU,YAAY,WAAW,gBAAgB,KAAG,eAS9E,CAAA"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../../src/resolvers/abilities/utils.ts"],"names":[],"mappings":"AACA,OAAO,EACL,gBAAgB,EAChB,YAAY,EAEZ,eAAe,EAEf,kBAAkB,EAInB,MAAM,4BAA4B,CAAA;AAkDnC,eAAO,MAAM,sBAAsB,aAAc,MAAM,KAAG,OACqB,CAAA;AAqE/E,eAAO,MAAM,yBAAyB,0BAA0B,CAAA;AAShE,eAAO,MAAM,eAAe,sBAAsB,CAAA;AAqDlD,eAAO,MAAM,WAAW,SAChB,eAAe,WACZ,gBAAgB,iBACX,OAAO,eACR,eAAe,oBACV,OAAO,MAAM,EAAE,kBAAkB,CAAC,KACnD,eAoVF,CAAA;AA0FD,eAAO,MAAM,cAAc,SAAU,YAAY,WAAW,gBAAgB,KAAG,eAS9E,CAAA"}