@dotcms/angular 0.0.1-alpha.38 → 0.0.1-alpha.39

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.
Files changed (78) hide show
  1. package/.eslintrc.json +18 -0
  2. package/jest.config.ts +22 -0
  3. package/ng-package.json +7 -0
  4. package/package.json +4 -21
  5. package/project.json +33 -0
  6. package/{index.d.ts → src/index.ts} +0 -1
  7. package/src/lib/components/dot-editable-text/dot-editable-text.component.css +4 -0
  8. package/src/lib/components/dot-editable-text/dot-editable-text.component.html +8 -0
  9. package/src/lib/components/dot-editable-text/dot-editable-text.component.spec.ts +424 -0
  10. package/src/lib/components/dot-editable-text/dot-editable-text.component.ts +269 -0
  11. package/src/lib/components/dot-editable-text/utils.ts +51 -0
  12. package/src/lib/components/no-component/no-component.component.css +3 -0
  13. package/src/lib/components/no-component/no-component.component.spec.ts +24 -0
  14. package/src/lib/components/no-component/no-component.component.ts +31 -0
  15. package/src/lib/layout/column/column.component.css +99 -0
  16. package/src/lib/layout/column/column.component.spec.ts +33 -0
  17. package/src/lib/layout/column/column.component.ts +49 -0
  18. package/src/lib/layout/container/container.component.css +9 -0
  19. package/src/lib/layout/container/container.component.html +26 -0
  20. package/src/lib/layout/container/container.component.spec.ts +205 -0
  21. package/src/lib/layout/container/container.component.ts +140 -0
  22. package/src/lib/layout/contentlet/contentlet.component.spec.ts +22 -0
  23. package/{lib/layout/contentlet/contentlet.component.d.ts → src/lib/layout/contentlet/contentlet.component.ts} +32 -17
  24. package/src/lib/layout/dotcms-layout/dotcms-layout.component.css +3 -0
  25. package/src/lib/layout/dotcms-layout/dotcms-layout.component.spec.ts +195 -0
  26. package/src/lib/layout/dotcms-layout/dotcms-layout.component.ts +150 -0
  27. package/src/lib/layout/row/row.component.css +6 -0
  28. package/src/lib/layout/row/row.component.spec.ts +28 -0
  29. package/src/lib/layout/row/row.component.ts +32 -0
  30. package/{lib/models/dotcms.model.d.ts → src/lib/models/dotcms.model.ts} +21 -3
  31. package/{lib/models/index.d.ts → src/lib/models/index.ts} +8 -1
  32. package/{lib/services/dotcms-context/page-context.service.d.ts → src/lib/services/dotcms-context/page-context.service.ts} +41 -12
  33. package/src/lib/services/dotcms-context/page-context.spec.ts +80 -0
  34. package/src/lib/utils/index.ts +92 -0
  35. package/src/lib/utils/testing.utils.ts +1019 -0
  36. package/src/test-setup.ts +8 -0
  37. package/tsconfig.json +29 -0
  38. package/tsconfig.lib.json +12 -0
  39. package/tsconfig.lib.prod.json +9 -0
  40. package/tsconfig.spec.json +11 -0
  41. package/dotcms-angular.d.ts.map +0 -1
  42. package/esm2022/dotcms-angular.mjs +0 -5
  43. package/esm2022/index.mjs +0 -5
  44. package/esm2022/lib/components/dot-editable-text/dot-editable-text.component.mjs +0 -225
  45. package/esm2022/lib/components/dot-editable-text/utils.mjs +0 -43
  46. package/esm2022/lib/components/no-component/no-component.component.mjs +0 -32
  47. package/esm2022/lib/layout/column/column.component.mjs +0 -45
  48. package/esm2022/lib/layout/container/container.component.mjs +0 -126
  49. package/esm2022/lib/layout/contentlet/contentlet.component.mjs +0 -120
  50. package/esm2022/lib/layout/dotcms-layout/dotcms-layout.component.mjs +0 -100
  51. package/esm2022/lib/layout/row/row.component.mjs +0 -29
  52. package/esm2022/lib/models/dotcms.model.mjs +0 -3
  53. package/esm2022/lib/models/index.mjs +0 -3
  54. package/esm2022/lib/services/dotcms-context/page-context.service.mjs +0 -75
  55. package/esm2022/lib/utils/index.mjs +0 -79
  56. package/fesm2022/dotcms-angular.mjs +0 -858
  57. package/fesm2022/dotcms-angular.mjs.map +0 -1
  58. package/index.d.ts.map +0 -1
  59. package/lib/components/dot-editable-text/dot-editable-text.component.d.ts +0 -129
  60. package/lib/components/dot-editable-text/dot-editable-text.component.d.ts.map +0 -1
  61. package/lib/components/dot-editable-text/utils.d.ts +0 -7
  62. package/lib/components/dot-editable-text/utils.d.ts.map +0 -1
  63. package/lib/components/no-component/no-component.component.d.ts +0 -22
  64. package/lib/components/no-component/no-component.component.d.ts.map +0 -1
  65. package/lib/layout/column/column.component.d.ts +0 -29
  66. package/lib/layout/column/column.component.d.ts.map +0 -1
  67. package/lib/layout/container/container.component.d.ts +0 -88
  68. package/lib/layout/container/container.component.d.ts.map +0 -1
  69. package/lib/layout/contentlet/contentlet.component.d.ts.map +0 -1
  70. package/lib/layout/dotcms-layout/dotcms-layout.component.d.ts +0 -67
  71. package/lib/layout/dotcms-layout/dotcms-layout.component.d.ts.map +0 -1
  72. package/lib/layout/row/row.component.d.ts +0 -20
  73. package/lib/layout/row/row.component.d.ts.map +0 -1
  74. package/lib/models/dotcms.model.d.ts.map +0 -1
  75. package/lib/models/index.d.ts.map +0 -1
  76. package/lib/services/dotcms-context/page-context.service.d.ts.map +0 -1
  77. package/lib/utils/index.d.ts +0 -63
  78. package/lib/utils/index.d.ts.map +0 -1
@@ -0,0 +1,205 @@
1
+ import { Spectator, byTestId, byText, createComponentFactory } from '@ngneat/spectator';
2
+ import { of } from 'rxjs';
3
+
4
+ import { Component, Input } from '@angular/core';
5
+
6
+ import { ContainerComponent } from './container.component';
7
+
8
+ import { NoComponent } from '../../components/no-component/no-component.component';
9
+ import { DotCMSContainer, DotCMSContentlet } from '../../models';
10
+ import { PageContextService } from '../../services/dotcms-context/page-context.service';
11
+ import { PageResponseMock } from '../../utils/testing.utils';
12
+
13
+ @Component({
14
+ selector: 'dotcms-mock-component',
15
+ standalone: true,
16
+ template: 'Hello world'
17
+ })
18
+ class DotcmsSDKMockComponent {
19
+ @Input() contentlet!: DotCMSContentlet;
20
+ }
21
+
22
+ @Component({
23
+ selector: 'dot-no-component',
24
+ template: 'no component yet - Custom'
25
+ })
26
+ class CustomNoComponent {
27
+ @Input() contentlet!: DotCMSContentlet;
28
+ }
29
+
30
+ describe('ContainerComponent', () => {
31
+ let spectator: Spectator<ContainerComponent>;
32
+
33
+ describe('inside editor', () => {
34
+ const createComponent = createComponentFactory({
35
+ component: ContainerComponent,
36
+ detectChanges: false,
37
+ providers: [
38
+ {
39
+ provide: PageContextService,
40
+ useValue: {
41
+ context: {
42
+ pageAsset: {
43
+ containers: PageResponseMock.containers
44
+ },
45
+ components: {
46
+ Banner: of(DotcmsSDKMockComponent)
47
+ },
48
+ isInsideEditor: true
49
+ }
50
+ }
51
+ }
52
+ ]
53
+ });
54
+
55
+ beforeEach(() => {
56
+ spectator = createComponent({
57
+ props: {
58
+ container: PageResponseMock.layout.body.rows[0].columns[0]
59
+ .containers[0] as DotCMSContainer
60
+ }
61
+ });
62
+ });
63
+
64
+ it('should render MockContainerComponent', () => {
65
+ spectator.detectChanges();
66
+ expect(spectator.query(DotcmsSDKMockComponent)).toBeTruthy();
67
+ });
68
+
69
+ it('should container have data attributes', () => {
70
+ spectator.detectChanges();
71
+ const container = spectator.debugElement.nativeElement;
72
+ expect(container?.getAttribute('data-dot-accept-types')).toBeDefined();
73
+ expect(container?.getAttribute('data-dot-identifier')).toBeDefined();
74
+ expect(container?.getAttribute('data-max-contentlets')).toBeDefined();
75
+ expect(container?.getAttribute('ata-dot-uuid')).toBeDefined();
76
+ });
77
+
78
+ it('should contentlets have data attributes', () => {
79
+ spectator.detectChanges();
80
+ const contentlets = spectator.queryAll(byTestId('dot-contentlet'));
81
+ contentlets.forEach((contentlet) => {
82
+ expect(contentlet.getAttribute('data-dot-identifier')).toBeDefined();
83
+ expect(contentlet.getAttribute('data-dot-basetype')).toBeDefined();
84
+ expect(contentlet.getAttribute('data-dot-title')).toBeDefined();
85
+ expect(contentlet.getAttribute('data-dot-inode')).toBeDefined();
86
+ expect(contentlet.getAttribute('data-dot-type')).toBeDefined();
87
+ expect(contentlet.getAttribute('data-dot-container')).toBeDefined();
88
+ expect(contentlet.getAttribute('data-dot-on-number-of-pages')).toBeDefined();
89
+ });
90
+ });
91
+
92
+ it('should render NoComponent component for unsetted content types', () => {
93
+ spectator.setInput(
94
+ 'container',
95
+ PageResponseMock.layout.body.rows[1].columns[0].containers[0] as DotCMSContainer
96
+ );
97
+ spectator.detectChanges();
98
+ expect(spectator.query(NoComponent)).toBeTruthy();
99
+ });
100
+
101
+ it('should render message when container is empty', () => {
102
+ spectator.setInput(
103
+ 'container',
104
+ PageResponseMock.layout.body.rows[2].columns[0].containers[0] as DotCMSContainer
105
+ );
106
+ spectator.detectChanges();
107
+ expect(spectator.query(byText('This container is empty.'))).toBeTruthy();
108
+ });
109
+ });
110
+
111
+ describe('with custom NoComponent component', () => {
112
+ const createComponent = createComponentFactory({
113
+ component: ContainerComponent,
114
+ detectChanges: false,
115
+ providers: [
116
+ {
117
+ provide: PageContextService,
118
+ useValue: {
119
+ context: {
120
+ pageAsset: {
121
+ containers: PageResponseMock.containers
122
+ },
123
+ components: {
124
+ Banner: of(DotcmsSDKMockComponent)
125
+ },
126
+ isInsideEditor: true
127
+ }
128
+ }
129
+ }
130
+ ]
131
+ });
132
+ beforeEach(() => {
133
+ spectator = createComponent({
134
+ props: {
135
+ container: PageResponseMock.layout.body.rows[1].columns[0]
136
+ .containers[0] as DotCMSContainer
137
+ },
138
+ providers: [
139
+ {
140
+ provide: PageContextService,
141
+ useValue: {
142
+ context: {
143
+ pageAsset: {
144
+ containers: PageResponseMock.containers
145
+ },
146
+ components: {
147
+ CustomNoComponent: of(CustomNoComponent)
148
+ },
149
+ isInsideEditor: true
150
+ }
151
+ }
152
+ }
153
+ ]
154
+ });
155
+ });
156
+
157
+ it('should render custom NoComponent component for unsetted content types', () => {
158
+ spectator.detectChanges();
159
+ expect(spectator.query(CustomNoComponent)).toBeTruthy();
160
+ });
161
+ });
162
+
163
+ describe('outside editor', () => {
164
+ const createComponent = createComponentFactory({
165
+ component: ContainerComponent,
166
+ detectChanges: false,
167
+ providers: [
168
+ {
169
+ provide: PageContextService,
170
+ useValue: {
171
+ context: {
172
+ pageAsset: {
173
+ containers: PageResponseMock.containers
174
+ },
175
+ components: {
176
+ Banner: of(DotcmsSDKMockComponent)
177
+ },
178
+ isInsideEditor: false
179
+ }
180
+ }
181
+ }
182
+ ]
183
+ });
184
+
185
+ beforeEach(() => {
186
+ spectator = createComponent({
187
+ props: {
188
+ container: PageResponseMock.layout.body.rows[0].columns[0]
189
+ .containers[0] as DotCMSContainer
190
+ }
191
+ });
192
+ });
193
+
194
+ it('should not have data attributes', () => {
195
+ spectator.detectChanges();
196
+ const container = spectator.query(byTestId('dot-container'));
197
+ expect(container?.getAttribute('data-dot-accept-types')).toBeUndefined();
198
+
199
+ const contentlets = spectator.queryAll(byTestId('dot-contentlet'));
200
+ contentlets.forEach((contentlet) => {
201
+ expect(contentlet.getAttribute('data-dot-identifier')).toBeUndefined();
202
+ });
203
+ });
204
+ });
205
+ });
@@ -0,0 +1,140 @@
1
+ import { AsyncPipe, NgComponentOutlet } from '@angular/common';
2
+ import {
3
+ ChangeDetectionStrategy,
4
+ Component,
5
+ HostBinding,
6
+ Input,
7
+ OnChanges,
8
+ computed,
9
+ inject,
10
+ signal
11
+ } from '@angular/core';
12
+
13
+ import { NoComponent } from '../../components/no-component/no-component.component';
14
+ import { DynamicComponentEntity } from '../../models';
15
+ import { DotCMSContainer, DotCMSContentlet } from '../../models/dotcms.model';
16
+ import { PageContextService } from '../../services/dotcms-context/page-context.service';
17
+ import { getContainersData } from '../../utils';
18
+ import { ContentletComponent } from '../contentlet/contentlet.component';
19
+
20
+ interface DotContainer {
21
+ acceptTypes: string;
22
+ identifier: string;
23
+ maxContentlets: number;
24
+ uuid: string;
25
+ variantId?: string;
26
+ }
27
+
28
+ /**
29
+ * This component is responsible to display a container with contentlets.
30
+ *
31
+ * @export
32
+ * @class ContainerComponent
33
+ * @implements {OnChanges}
34
+ */
35
+ @Component({
36
+ selector: 'dotcms-container',
37
+ standalone: true,
38
+ imports: [AsyncPipe, NgComponentOutlet, NoComponent, ContentletComponent],
39
+ templateUrl: './container.component.html',
40
+ styleUrl: './container.component.css',
41
+ changeDetection: ChangeDetectionStrategy.OnPush
42
+ })
43
+ export class ContainerComponent implements OnChanges {
44
+ /**
45
+ * The container object containing the contentlets.
46
+ *
47
+ * @type {DotCMSContainer}
48
+ * @memberof ContainerComponent
49
+ */
50
+ @Input({ required: true }) container!: DotCMSContainer;
51
+
52
+ private readonly pageContextService: PageContextService = inject(PageContextService);
53
+ protected readonly NoComponent = NoComponent;
54
+ protected readonly $isInsideEditor = signal<boolean>(false);
55
+
56
+ protected componentsMap!: Record<string, DynamicComponentEntity>;
57
+ protected $contentlets = signal<DotCMSContentlet[]>([]);
58
+ protected $dotContainer = signal<DotContainer | null>(null);
59
+ protected $dotContainerAsString = computed(() => JSON.stringify(this.$dotContainer()));
60
+
61
+ /**
62
+ * The accept types for the container component.
63
+ *
64
+ * @type {(string | null)}
65
+ * @memberof ContainerComponent
66
+ */
67
+ @HostBinding('attr.data-dot-accept-types') acceptTypes: string | null = null;
68
+
69
+ /**
70
+ * The identifier for the container component.
71
+ *
72
+ * @type {(string | null)}
73
+ * @memberof ContainerComponent
74
+ */
75
+ @HostBinding('attr.data-dot-identifier') identifier: string | null = null;
76
+ /**
77
+ * The max contentlets for the container component.
78
+ *
79
+ * @type {(number | null)}
80
+ * @memberof ContainerComponent
81
+ */
82
+ @HostBinding('attr.data-max-contentlets') maxContentlets: number | null = null;
83
+ /**
84
+ * The uuid for the container component.
85
+ *
86
+ * @type {(string | null)}
87
+ * @memberof ContainerComponent
88
+ */
89
+ @HostBinding('attr.data-dot-uuid') uuid: string | null = null;
90
+ /**
91
+ * The class for the container component.
92
+ *
93
+ * @type {(string | null)}
94
+ * @memberof ContainerComponent
95
+ */
96
+ @HostBinding('class') class: string | null = null;
97
+ /**
98
+ * The dot object for the container component.
99
+ *
100
+ * @type {(string | null)}
101
+ * @memberof ContainerComponent
102
+ */
103
+ @HostBinding('attr.data-dot-object') dotObject: string | null = null;
104
+ /**
105
+ * The data-testid attribute used for identifying the component during testing.
106
+ *
107
+ * @memberof ContainerComponent
108
+ */
109
+ @HostBinding('attr.data-testid') testId = 'dot-container';
110
+
111
+ ngOnChanges() {
112
+ const { pageAsset, components, isInsideEditor } = this.pageContextService.context;
113
+ const { acceptTypes, maxContentlets, variantId, path, contentlets } = getContainersData(
114
+ pageAsset.containers,
115
+ this.container
116
+ );
117
+ const { identifier, uuid } = this.container;
118
+
119
+ this.componentsMap = components;
120
+
121
+ this.$isInsideEditor.set(isInsideEditor);
122
+ this.$contentlets.set(contentlets);
123
+ this.$dotContainer.set({
124
+ identifier: path ?? identifier,
125
+ acceptTypes,
126
+ maxContentlets,
127
+ variantId,
128
+ uuid
129
+ });
130
+
131
+ if (this.$isInsideEditor()) {
132
+ this.acceptTypes = acceptTypes;
133
+ this.identifier = identifier;
134
+ this.maxContentlets = maxContentlets;
135
+ this.uuid = uuid;
136
+ this.class = this.$contentlets().length ? null : 'empty-container';
137
+ this.dotObject = 'container';
138
+ }
139
+ }
140
+ }
@@ -0,0 +1,22 @@
1
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
2
+
3
+ import { ContentletComponent } from './contentlet.component';
4
+
5
+ describe('ContentletComponent', () => {
6
+ let component: ContentletComponent;
7
+ let fixture: ComponentFixture<ContentletComponent>;
8
+
9
+ beforeEach(async () => {
10
+ await TestBed.configureTestingModule({
11
+ imports: [ContentletComponent]
12
+ }).compileComponents();
13
+
14
+ fixture = TestBed.createComponent(ContentletComponent);
15
+ component = fixture.componentInstance;
16
+ fixture.detectChanges();
17
+ });
18
+
19
+ it('should create', () => {
20
+ expect(component).toBeTruthy();
21
+ });
22
+ });
@@ -1,6 +1,7 @@
1
- import { OnChanges } from '@angular/core';
1
+ import { ChangeDetectionStrategy, Component, HostBinding, Input, OnChanges } from '@angular/core';
2
+
2
3
  import { DotCMSContentlet } from '../../models';
3
- import * as i0 from "@angular/core";
4
+
4
5
  /**
5
6
  * This component is responsible to display a contentlet.
6
7
  *
@@ -8,79 +9,93 @@ import * as i0 from "@angular/core";
8
9
  * @class ContentletComponent
9
10
  * @implements {OnChanges}
10
11
  */
11
- export declare class ContentletComponent implements OnChanges {
12
+ @Component({
13
+ selector: 'dotcms-contentlet-wrapper',
14
+ standalone: true,
15
+ template: '<ng-content></ng-content>',
16
+ changeDetection: ChangeDetectionStrategy.OnPush
17
+ })
18
+ export class ContentletComponent implements OnChanges {
12
19
  /**
13
20
  * The contentlet object containing content data.
14
21
  *
15
22
  * @type {DotCMSContentlet}
16
23
  * @memberof ContentletComponent
17
24
  */
18
- contentlet: DotCMSContentlet;
25
+ @Input({ required: true }) contentlet!: DotCMSContentlet;
19
26
  /**
20
27
  * The container data (as string) where the contentlet is located.
21
28
  *
22
29
  * @type {string}
23
30
  * @memberof ContentletComponent
24
31
  */
25
- container: string;
32
+ @Input() container!: string;
33
+
26
34
  /**
27
35
  * The identifier of contentlet component.
28
36
  *
29
37
  * @type {(string | null)}
30
38
  * @memberof ContentletComponent
31
39
  */
32
- identifier: string | null;
40
+ @HostBinding('attr.data-dot-identifier') identifier: string | null = null;
33
41
  /**
34
42
  * The base type of contentlet component.
35
43
  *
36
44
  * @type {(string | null)}
37
45
  * @memberof ContentletComponent
38
46
  */
39
- baseType: string | null;
47
+ @HostBinding('attr.data-dot-basetype') baseType: string | null = null;
40
48
  /**
41
49
  * The title of contentlet component.
42
50
  *
43
51
  * @type {(string | null)}
44
52
  * @memberof ContentletComponent
45
53
  */
46
- title: string | null;
54
+ @HostBinding('attr.data-dot-title') title: string | null = null;
47
55
  /**
48
56
  * The inode of contentlet component.
49
57
  *
50
58
  * @type {(string | null)}
51
59
  * @memberof ContentletComponent
52
60
  */
53
- inode: string | null;
61
+ @HostBinding('attr.data-dot-inode') inode: string | null = null;
54
62
  /**
55
63
  * The type of contentlet component.
56
64
  *
57
65
  * @type {(string | null)}
58
66
  * @memberof ContentletComponent
59
67
  */
60
- dotType: string | null;
68
+ @HostBinding('attr.data-dot-type') dotType: string | null = null;
61
69
  /**
62
70
  * The container of contentlet component.
63
71
  *
64
72
  * @type {(string | null)}
65
73
  * @memberof ContentletComponent
66
74
  */
67
- dotContainer: string | null;
75
+ @HostBinding('attr.data-dot-container') dotContainer: string | null = null;
68
76
  /**
69
77
  * The number of pages where the contentlet appears
70
78
  *
71
79
  * @type {(string | null)}
72
80
  * @memberof ContentletComponent
73
81
  */
74
- numberOfPages: string | null;
82
+ @HostBinding('attr.data-dot-on-number-of-pages') numberOfPages: string | null = null;
75
83
  /**
76
84
  * The content of contentlet component.
77
85
  *
78
86
  * @type {(string | null)}
79
87
  * @memberof ContentletComponent
80
88
  */
81
- dotContent: string | null;
82
- ngOnChanges(): void;
83
- static ɵfac: i0.ɵɵFactoryDeclaration<ContentletComponent, never>;
84
- static ɵcmp: i0.ɵɵComponentDeclaration<ContentletComponent, "dotcms-contentlet-wrapper", never, { "contentlet": { "alias": "contentlet"; "required": true; }; "container": { "alias": "container"; "required": false; }; }, {}, never, ["*"], true, never>;
89
+ @HostBinding('attr.data-dot-object') dotContent: string | null = null;
90
+
91
+ ngOnChanges() {
92
+ this.identifier = this.contentlet.identifier;
93
+ this.baseType = this.contentlet.baseType;
94
+ this.title = this.contentlet.title;
95
+ this.inode = this.contentlet.inode;
96
+ this.dotType = this.contentlet.contentType;
97
+ this.dotContainer = this.container;
98
+ this.numberOfPages = this.contentlet['onNumberOfPages'];
99
+ this.dotContent = 'contentlet';
100
+ }
85
101
  }
86
- //# sourceMappingURL=contentlet.component.d.ts.map
@@ -0,0 +1,3 @@
1
+ :host {
2
+ display: block;
3
+ }
@@ -0,0 +1,195 @@
1
+ import { expect } from '@jest/globals';
2
+ import { Spectator, createRoutingFactory } from '@ngneat/spectator/jest';
3
+ import { MockComponent } from 'ng-mocks';
4
+ import { of } from 'rxjs';
5
+
6
+ import { Component, Input } from '@angular/core';
7
+ import { ActivatedRoute, Router } from '@angular/router';
8
+
9
+ import * as dotcmsClient from '@dotcms/client';
10
+
11
+ import { PageResponseMock, PageResponseOneRowMock } from './../../utils/testing.utils';
12
+ import { DotcmsLayoutComponent } from './dotcms-layout.component';
13
+
14
+ import { DotCMSContentlet, DotCMSPageAsset } from '../../models';
15
+ import { PageContextService } from '../../services/dotcms-context/page-context.service';
16
+ import { RowComponent } from '../row/row.component';
17
+
18
+ interface Callback {
19
+ [key: string]: (data: unknown) => void;
20
+ }
21
+
22
+ interface DotCmsClientMock extends dotcmsClient.DotCmsClient {
23
+ editor: {
24
+ on: (type: string, callbackFn: (data: unknown) => void) => void;
25
+ off: jest.Mock;
26
+ callbacks: Callback;
27
+ };
28
+ }
29
+
30
+ @Component({
31
+ selector: 'dotcms-mock-component',
32
+ standalone: true,
33
+ template: 'Hello world'
34
+ })
35
+ class DotcmsSDKMockComponent {
36
+ @Input() contentlet!: DotCMSContentlet;
37
+ }
38
+
39
+ jest.mock('@dotcms/client', () => ({
40
+ ...jest.requireActual('@dotcms/client'),
41
+ isInsideEditor: jest.fn().mockReturnValue(true),
42
+ initEditor: jest.fn(),
43
+ updateNavigation: jest.fn(),
44
+ postMessageToEditor: jest.fn(),
45
+ DotCmsClient: {
46
+ instance: {
47
+ editor: {
48
+ on: function (type: string, callbackFn: (data: unknown) => void): void {
49
+ this.callbacks[type] = callbackFn;
50
+ },
51
+ off: jest.fn(),
52
+ callbacks: {} as Callback
53
+ }
54
+ }
55
+ },
56
+ CUSTOMER_ACTIONS: {
57
+ GET_PAGE_DATA: 'get-page-data'
58
+ }
59
+ }));
60
+
61
+ const { DotCmsClient } = dotcmsClient as jest.Mocked<typeof dotcmsClient>;
62
+
63
+ describe('DotcmsLayoutComponent', () => {
64
+ let spectator: Spectator<DotcmsLayoutComponent>;
65
+ let pageContextService: PageContextService;
66
+
67
+ const createComponent = createRoutingFactory({
68
+ component: DotcmsLayoutComponent,
69
+ imports: [MockComponent(RowComponent)],
70
+ providers: [
71
+ PageContextService,
72
+ { provide: ActivatedRoute, useValue: { url: of([]) } },
73
+ { provide: Router, useValue: {} }
74
+ ]
75
+ });
76
+
77
+ beforeEach(() => {
78
+ spectator = createComponent({
79
+ props: {
80
+ pageAsset: PageResponseMock as unknown as DotCMSPageAsset,
81
+ components: {
82
+ Banner: Promise.resolve(DotcmsSDKMockComponent)
83
+ }
84
+ },
85
+ detectChanges: false
86
+ });
87
+
88
+ pageContextService = spectator.inject(PageContextService, true);
89
+ });
90
+
91
+ afterEach(() => {
92
+ jest.clearAllMocks();
93
+ });
94
+
95
+ it('should render rows', () => {
96
+ spectator.detectChanges();
97
+ expect(spectator.queryAll(RowComponent).length).toBe(3);
98
+ });
99
+
100
+ it('should save pageContext', () => {
101
+ const setContextSpy = jest.spyOn(pageContextService, 'setContext');
102
+ spectator.detectChanges();
103
+ expect(setContextSpy).toHaveBeenCalled();
104
+ });
105
+
106
+ describe('inside editor', () => {
107
+ it('should call initEditor and updateNavigation from @dotcms/client', () => {
108
+ const initEditorSpy = jest.spyOn(dotcmsClient, 'initEditor');
109
+ const updateNavigationSpy = jest.spyOn(dotcmsClient, 'updateNavigation');
110
+
111
+ spectator.detectChanges();
112
+ expect(initEditorSpy).toHaveBeenCalled();
113
+ expect(updateNavigationSpy).toHaveBeenCalled();
114
+ });
115
+
116
+ describe('onReload', () => {
117
+ const client = DotCmsClient.instance;
118
+ let editorOnSpy: jest.SpyInstance;
119
+
120
+ beforeEach(() => {
121
+ editorOnSpy = jest.spyOn(client.editor, 'on');
122
+ spectator.setInput('onReload', () => {
123
+ /* do nothing */
124
+ });
125
+ spectator.detectChanges();
126
+ });
127
+
128
+ it('should subscribe to the `CHANGE` event', () => {
129
+ expect(editorOnSpy).toHaveBeenCalled();
130
+ });
131
+
132
+ it('should remove listener on unmount', () => {
133
+ spectator.component.ngOnDestroy();
134
+ spectator.detectChanges();
135
+
136
+ expect(client.editor.off).toHaveBeenCalledWith('changes');
137
+ });
138
+ });
139
+
140
+ describe('client is ready', () => {
141
+ const query = { query: 'query { ... }' };
142
+
143
+ beforeEach(() => {
144
+ spectator.setInput('editor', query);
145
+ spectator.detectChanges();
146
+ });
147
+
148
+ it('should post message to editor', () => {
149
+ spectator.detectChanges();
150
+ expect(dotcmsClient.postMessageToEditor).toHaveBeenCalledWith({
151
+ action: dotcmsClient.CUSTOMER_ACTIONS.CLIENT_READY,
152
+ payload: query
153
+ });
154
+ });
155
+ });
156
+
157
+ describe('onChange', () => {
158
+ const client = DotCmsClient.instance;
159
+ beforeEach(() => spectator.detectChanges());
160
+
161
+ it('should update the page asset when changes are made in the editor', () => {
162
+ const editorOnSpy = jest.spyOn(client.editor, 'on');
163
+ expect(editorOnSpy).toHaveBeenCalledWith('changes', expect.any(Function));
164
+ });
165
+ });
166
+ });
167
+
168
+ describe('template', () => {
169
+ beforeEach(() => spectator.detectChanges());
170
+
171
+ it('should render rows', () => {
172
+ expect(spectator.queryAll(RowComponent).length).toBe(3);
173
+ });
174
+
175
+ it('should pass the correct row to RowComponent', () => {
176
+ const rowComponents = spectator.queryAll(RowComponent);
177
+ const rows = PageResponseMock.layout.body.rows;
178
+ expect(rowComponents.length).toBe(rows.length);
179
+
180
+ rowComponents.forEach((component, index) => {
181
+ expect(component.row).toEqual(rows[index]);
182
+ });
183
+ });
184
+
185
+ it('should update the page asset when changes are made in the editor', () => {
186
+ const { editor } = DotCmsClient.instance as DotCmsClientMock;
187
+ editor.callbacks['changes'](PageResponseOneRowMock);
188
+ spectator.detectChanges();
189
+ const rowComponents = spectator.queryAll(RowComponent);
190
+ const rows = PageResponseMock.layout.body.rows;
191
+ expect(rowComponents.length).toBe(1);
192
+ expect(rowComponents[0].row).toEqual(rows[0]);
193
+ });
194
+ });
195
+ });