@exdst-sitecore-content-sdk/astro 0.0.16 → 0.0.19

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 (110) hide show
  1. package/README.md +3 -3
  2. package/package.json +41 -42
  3. package/src/client/index.ts +1 -1
  4. package/src/client/sitecore-astro-client.test.ts +41 -20
  5. package/src/client/sitecore-astro-client.ts +74 -57
  6. package/src/components/AstroImage.astro +2 -2
  7. package/src/components/AstroImage.astro.test.ts +542 -0
  8. package/src/components/Date.astro +5 -1
  9. package/src/components/Date.astro.test.ts +197 -0
  10. package/src/components/DefaultEmptyFieldEditingComponentImage.astro +4 -0
  11. package/src/components/DefaultEmptyFieldEditingComponentText.astro +4 -0
  12. package/src/components/EditingScripts.astro +2 -2
  13. package/src/components/EditingScripts.astro.test.ts +267 -0
  14. package/src/components/ErrorBoundary.astro +8 -9
  15. package/src/components/ErrorBoundary.astro.test.ts +252 -0
  16. package/src/components/ErrorComponent.astro +16 -0
  17. package/src/components/ErrorComponent.astro.test.ts +31 -0
  18. package/src/components/FieldMetadata.astro +1 -1
  19. package/src/components/FieldMetadata.astro.test.ts +40 -0
  20. package/src/components/File.astro +5 -1
  21. package/src/components/File.astro.test.ts +68 -0
  22. package/src/components/HiddenRendering.astro.test.ts +36 -0
  23. package/src/components/Image.astro +18 -4
  24. package/src/components/Image.astro.test.ts +438 -0
  25. package/src/components/Link.astro +13 -1
  26. package/src/components/Link.astro.test.ts +261 -0
  27. package/src/components/MissingComponent.astro.test.ts +21 -0
  28. package/src/components/Placeholder/Placeholder.astro +18 -23
  29. package/src/components/Placeholder/Placeholder.astro.test.ts +1088 -0
  30. package/src/components/Placeholder/PlaceholderMetadata.astro +24 -18
  31. package/src/components/Placeholder/PlaceholderMetadata.astro.test.ts +228 -0
  32. package/src/components/Placeholder/PlaceholderUtils.astro +21 -40
  33. package/src/components/Placeholder/PlaceholderUtils.astro.test.ts +149 -0
  34. package/src/components/Placeholder/models.ts +26 -17
  35. package/src/components/Placeholder/placeholder-utils.test.ts +153 -6
  36. package/src/components/Placeholder/placeholder-utils.ts +33 -11
  37. package/src/components/RichText.astro +9 -1
  38. package/src/components/RichText.astro.test.ts +205 -0
  39. package/src/components/Text.astro +15 -3
  40. package/src/components/Text.astro.test.ts +273 -0
  41. package/src/config/define-config.test.ts +5 -5
  42. package/src/config/define-config.ts +22 -42
  43. package/src/config-cli/define-cli-config.test.ts +5 -12
  44. package/src/config-cli/define-cli-config.ts +4 -8
  45. package/src/context.ts +42 -11
  46. package/src/debug.ts +13 -0
  47. package/src/editing/editing-config-middleware.test.ts +5 -7
  48. package/src/editing/editing-config-middleware.ts +11 -7
  49. package/src/editing/editing-render-middleware.test.ts +366 -24
  50. package/src/editing/editing-render-middleware.ts +34 -12
  51. package/src/editing/index.ts +2 -0
  52. package/src/editing/render-middleware.test.ts +1 -1
  53. package/src/editing/render-middleware.ts +1 -1
  54. package/src/editing/types.ts +39 -0
  55. package/src/editing/utils.test.ts +364 -4
  56. package/src/editing/utils.ts +82 -24
  57. package/src/enhancers/WithEmptyFieldEditingComponent.astro +1 -1
  58. package/src/enhancers/WithEmptyFieldEditingComponent.astro.test.ts +380 -0
  59. package/src/enhancers/WithFieldMetadata.astro.test.ts +113 -0
  60. package/src/index.ts +10 -7
  61. package/src/middleware/index.ts +4 -12
  62. package/src/middleware/middleware.test.ts +13 -0
  63. package/src/middleware/middleware.ts +12 -3
  64. package/src/middleware/multisite-middleware.test.ts +45 -50
  65. package/src/middleware/multisite-middleware.ts +33 -6
  66. package/src/middleware/robots-middleware.test.ts +20 -4
  67. package/src/middleware/robots-middleware.ts +10 -3
  68. package/src/middleware/sitemap-middleware.test.ts +35 -3
  69. package/src/middleware/sitemap-middleware.ts +7 -6
  70. package/src/services/component-props-service.ts +7 -6
  71. package/src/sharedTypes/component-props.ts +15 -4
  72. package/src/site/index.ts +1 -1
  73. package/src/tests/astro-helpers.ts +61 -0
  74. package/src/tests/test-components/CustomErrorComponent.astro +3 -0
  75. package/src/tests/test-components/CustomHiddenRendering.astro +10 -0
  76. package/src/tests/test-components/CustomMissingComponent.astro +9 -0
  77. package/src/tests/test-components/DownloadCallout.astro +12 -0
  78. package/src/tests/test-components/EmptyFieldEditingComponent.astro +5 -0
  79. package/src/tests/test-components/ErrorBoundaryWithError.astro +10 -0
  80. package/src/tests/test-components/Home.astro +12 -0
  81. package/src/tests/test-components/SxaRichText.astro +23 -0
  82. package/src/tests/test-components/SxaRichTextDefault.astro +7 -0
  83. package/src/tests/test-components/SxaRichTextWithTitle.astro +8 -0
  84. package/src/tests/test-components/TestComponent.astro +9 -0
  85. package/src/tests/test-components/TestComponentWithError.astro +4 -0
  86. package/src/tests/test-components/TestComponentWithField.astro +17 -0
  87. package/src/tests/test-components/TestHeader.astro +8 -0
  88. package/src/tests/test-components/TestLogo.astro +5 -0
  89. package/src/tests/test-components/TestParentWrapperComponent.astro +5 -0
  90. package/src/tests/test-components/TestWrapperComponent.astro +5 -0
  91. package/src/tests/test-data/metadata-data.ts +86 -0
  92. package/src/tests/test-data/normal-mode-data.ts +466 -0
  93. package/src/tests/vitest.setup.ts +4 -0
  94. package/src/tools/generate-map.ts +4 -3
  95. package/src/tools/index.ts +2 -4
  96. package/src/tools/templating/components.test.ts +100 -87
  97. package/src/tools/templating/components.ts +2 -1
  98. package/src/tools/templating/default-component.ts +3 -8
  99. package/src/utils/utils.ts +20 -2
  100. /package/src/{test-data → tests}/helpers.ts +0 -0
  101. /package/src/{test-data → tests}/personalizeData.ts +0 -0
  102. /package/src/{test-data/components → tests/test-components/map-components}/Bar.astro +0 -0
  103. /package/src/{test-data/components → tests/test-components/map-components}/Baz.astro +0 -0
  104. /package/src/{test-data/components → tests/test-components/map-components}/Foo.astro +0 -0
  105. /package/src/{test-data/components → tests/test-components/map-components}/Hero.variant.astro +0 -0
  106. /package/src/{test-data/components → tests/test-components/map-components}/NotComponent.bsx +0 -0
  107. /package/src/{test-data/components → tests/test-components/map-components}/Qux.astro +0 -0
  108. /package/src/{test-data/components → tests/test-components/map-components}/folded/Folded.astro +0 -0
  109. /package/src/{test-data/components → tests/test-components/map-components}/folded/random-file-2.docx +0 -0
  110. /package/src/{test-data/components → tests/test-components/map-components}/random-file.txt +0 -0
@@ -1,25 +1,28 @@
1
- /* eslint-disable no-unused-expressions */
1
+ /* eslint-disable no-unused-expressions */
2
2
  /* eslint-disable dot-notation */
3
3
  import * as chai from 'chai';
4
4
  import { use } from 'chai';
5
5
  import chaiString from 'chai-string';
6
6
  import sinonChai from 'sinon-chai';
7
7
  import sinon, { spy } from 'sinon';
8
- import { debug } from '@sitecore-content-sdk/core';
8
+ import debug from '../debug';
9
9
 
10
10
  import { MultisiteMiddleware } from './multisite-middleware';
11
- import { SiteInfo, SiteResolver } from '@sitecore-content-sdk/core/site';
11
+ import { SiteInfo, SiteResolver } from '@sitecore-content-sdk/content/site';
12
12
  import { APIContext, AstroCookieSetOptions } from 'astro';
13
13
 
14
14
  use(sinonChai);
15
15
  const expect = chai.use(chaiString).expect;
16
16
 
17
17
  describe('MultisiteMiddleware', () => {
18
- let debugSpy;
19
- const validateDebugLog = (message: string, ...params: any) =>
20
- expect(debugSpy.args.find((log) => log[0] === message)).to.deep.equal([message, ...params]);
18
+ let debugSpy!: ReturnType<typeof spy>;
19
+ const validateDebugLog = (message: string, ...params: any[]) =>
20
+ expect(debugSpy.args.find((log: any[]) => log[0] === message)).to.deep.equal([
21
+ message,
22
+ ...params,
23
+ ]);
21
24
  const validateEndMessageDebugLog = (message: string, params: any) => {
22
- const logParams = debugSpy.args.find((log) => log[0] === message) as Array<unknown>;
25
+ const logParams = debugSpy.args.find((log: any[]) => log[0] === message) as Array<unknown>;
23
26
 
24
27
  expect(logParams[2]).to.deep.include(params);
25
28
  };
@@ -47,7 +50,7 @@ describe('MultisiteMiddleware', () => {
47
50
  return headers[key];
48
51
  },
49
52
  append(key: string, value: string | Record<string, any>) {
50
- context.request.headers[key] = value;
53
+ context.request.headers.set(key, value as string);
51
54
  },
52
55
  ...props.headers,
53
56
  },
@@ -62,7 +65,7 @@ describe('MultisiteMiddleware', () => {
62
65
  value: string | Record<string, any>,
63
66
  options?: AstroCookieSetOptions
64
67
  ) {
65
- context.cookies[cookieName] = { value, ...options };
68
+ context.cookies.set(cookieName, value as string, options);
66
69
  },
67
70
  ...props?.cookies,
68
71
  ...props.cookieValues,
@@ -98,9 +101,9 @@ describe('MultisiteMiddleware', () => {
98
101
 
99
102
  Object.defineProperties(response.headers, {
100
103
  forEach: {
101
- value: (cb) => {
104
+ value: (cb: any) => {
102
105
  Object.keys(response.headers).forEach((key) =>
103
- cb(response.headers[key], key, response.headers)
106
+ cb(response.headers.get(key), key, response.headers)
104
107
  );
105
108
  },
106
109
  enumerable: false,
@@ -276,7 +279,7 @@ describe('MultisiteMiddleware', () => {
276
279
  rewritePath: '/_site_foobar/styleguide',
277
280
  siteName: 'foobar',
278
281
  headers: {
279
- ...finalRes.headers,
282
+ ...(finalRes as Response).headers,
280
283
  'x-sc-rewrite': '/_site_foobar/styleguide',
281
284
  },
282
285
  cookies: 'sc_site=foobar; HttpOnly; Secure; SameSite=None',
@@ -315,7 +318,7 @@ describe('MultisiteMiddleware', () => {
315
318
  rewritePath: '/_site_foobar/styleguide',
316
319
  siteName: 'foobar',
317
320
  headers: {
318
- ...finalRes.headers,
321
+ ...(finalRes as Response).headers,
319
322
  'x-sc-rewrite': '/_site_foobar/styleguide',
320
323
  },
321
324
  cookies: 'sc_site=foobar; HttpOnly; Secure; SameSite=None',
@@ -356,7 +359,7 @@ describe('MultisiteMiddleware', () => {
356
359
  rewritePath: '/_site_foo/styleguide',
357
360
  siteName: 'foo',
358
361
  headers: {
359
- ...finalRes.headers,
362
+ ...(finalRes as Response).headers,
360
363
  'x-sc-rewrite': '/_site_foo/styleguide',
361
364
  },
362
365
  cookies: 'sc_site=foo; HttpOnly; Secure; SameSite=None',
@@ -392,7 +395,7 @@ describe('MultisiteMiddleware', () => {
392
395
  rewritePath: '/_site_foo/styleguide',
393
396
  siteName: 'foo',
394
397
  headers: {
395
- ...finalRes.headers,
398
+ ...(finalRes as Response).headers,
396
399
  'x-sc-rewrite': '/_site_foo/styleguide',
397
400
  },
398
401
  cookies: 'sc_site=foo; HttpOnly; Secure; SameSite=None',
@@ -426,7 +429,7 @@ describe('MultisiteMiddleware', () => {
426
429
  rewritePath: '/_site_foo/styleguide',
427
430
  siteName: 'foo',
428
431
  headers: {
429
- ...finalRes.headers,
432
+ ...(finalRes as Response).headers,
430
433
  'x-sc-rewrite': '/_site_foo/styleguide',
431
434
  },
432
435
  cookies: 'sc_site=foo; HttpOnly; Secure; SameSite=None',
@@ -460,7 +463,7 @@ describe('MultisiteMiddleware', () => {
460
463
  rewritePath: '/_site_foo/styleguide',
461
464
  siteName: 'foo',
462
465
  headers: {
463
- ...finalRes.headers,
466
+ ...(finalRes as Response).headers,
464
467
  'x-sc-rewrite': '/_site_foo/styleguide',
465
468
  },
466
469
  cookies: 'sc_site=foo; HttpOnly; Secure; SameSite=None',
@@ -498,7 +501,7 @@ describe('MultisiteMiddleware', () => {
498
501
  rewritePath: '/_site_qsFoo/styleguide',
499
502
  siteName: 'qsFoo',
500
503
  headers: {
501
- ...finalRes.headers,
504
+ ...(finalRes as Response).headers,
502
505
  'x-sc-rewrite': '/_site_qsFoo/styleguide',
503
506
  },
504
507
  cookies: 'sc_site=qsFoo; HttpOnly; Secure; SameSite=None',
@@ -537,7 +540,7 @@ describe('MultisiteMiddleware', () => {
537
540
  rewritePath: '/_site_qsFoo/styleguide',
538
541
  siteName: 'qsFoo',
539
542
  headers: {
540
- ...finalRes.headers,
543
+ ...(finalRes as Response).headers,
541
544
  'x-sc-rewrite': '/_site_qsFoo/styleguide',
542
545
  },
543
546
  cookies: 'sc_site=qsFoo; HttpOnly; Secure; SameSite=None',
@@ -576,7 +579,7 @@ describe('MultisiteMiddleware', () => {
576
579
  rewritePath: '/_site_foobar/styleguide',
577
580
  siteName: 'foobar',
578
581
  headers: {
579
- ...finalRes.headers,
582
+ ...(finalRes as Response).headers,
580
583
  'x-sc-rewrite': '/_site_foobar/styleguide',
581
584
  },
582
585
  cookies: 'sc_site=foobar; HttpOnly; Secure; SameSite=None',
@@ -613,7 +616,7 @@ describe('MultisiteMiddleware', () => {
613
616
  rewritePath: '/_site_foo/styleguide',
614
617
  siteName: 'foo',
615
618
  headers: {
616
- ...finalRes.headers,
619
+ ...(finalRes as Response).headers,
617
620
  'x-sc-rewrite': '/_site_foo/styleguide',
618
621
  },
619
622
  cookies: 'sc_site=foo; HttpOnly; Secure; SameSite=None',
@@ -629,44 +632,36 @@ describe('MultisiteMiddleware', () => {
629
632
  const context = createContext();
630
633
  const res = createResponse();
631
634
 
632
- let errorSpy: sinon.SinonSpy<[message?: any, ...optionalParams: any[]], void>;
633
-
634
- before(() => {
635
- errorSpy = spy(console, 'log');
636
- });
637
-
638
- beforeEach(() => {
639
- errorSpy.resetHistory();
640
- });
635
+ it('should handle error', async () => {
636
+ const errorSpy = sinon.stub(console, 'log');
641
637
 
642
- after(() => {
643
- errorSpy.restore();
644
- });
638
+ try {
639
+ const error = new Error('Custom error');
645
640
 
646
- it('should handle error', async () => {
647
- const error = new Error('Custom error');
641
+ class SampleSiteResolver extends SiteResolver {
642
+ constructor(sites: SiteInfo[]) {
643
+ super(sites);
644
+ }
648
645
 
649
- class SampleSiteResolver extends SiteResolver {
650
- constructor(sites: SiteInfo[]) {
651
- super(sites);
646
+ getByHost = () => {
647
+ throw error;
648
+ };
652
649
  }
653
650
 
654
- getByHost = () => {
655
- throw error;
656
- };
657
- }
658
-
659
- const middleware = new MultisiteMiddleware({ ...defaultConfig });
660
- middleware['siteResolver'] = new SampleSiteResolver([]);
651
+ const middleware = new MultisiteMiddleware({ ...defaultConfig });
652
+ middleware['siteResolver'] = new SampleSiteResolver([]);
661
653
 
662
- const mockNext = sinon.stub().returns(res);
654
+ const mockNext = sinon.stub().returns(res);
663
655
 
664
- const finalRes = await middleware.handle(context, mockNext);
656
+ const finalRes = await middleware.handle(context, mockNext);
665
657
 
666
- expect(errorSpy.getCall(0).calledWith('Multisite middleware failed:')).to.be.true;
667
- expect(errorSpy.getCall(1).calledWith(error)).to.be.true;
658
+ expect(errorSpy.getCall(0).calledWith('Multisite middleware failed:')).to.be.true;
659
+ expect(errorSpy.getCall(1).calledWith(error)).to.be.true;
668
660
 
669
- expect(finalRes).to.deep.equal(res);
661
+ expect(finalRes).to.deep.equal(res);
662
+ } finally {
663
+ errorSpy.restore();
664
+ }
670
665
  });
671
666
  });
672
667
  });
@@ -1,9 +1,9 @@
1
- /* eslint-disable @typescript-eslint/no-non-null-asserted-optional-chain */
2
- import { getSiteRewrite, SITE_KEY } from '@sitecore-content-sdk/core/site';
3
- import { debug } from '@sitecore-content-sdk/core';
1
+ /* eslint-disable @typescript-eslint/no-non-null-asserted-optional-chain */
2
+ import { getSiteRewrite, SITE_KEY } from '@sitecore-content-sdk/content/site';
3
+ import debug from '../debug';
4
4
  import { MiddlewareBase, MiddlewareBaseConfig } from './middleware';
5
5
  import { SitecoreConfig } from '../config';
6
- import { PREVIEW_KEY } from '@sitecore-content-sdk/core/editing';
6
+ import { PREVIEW_KEY } from '@sitecore-content-sdk/content/editing';
7
7
  import { APIContext, MiddlewareHandler, MiddlewareNext } from 'astro';
8
8
  import * as cookie from 'cookie';
9
9
 
@@ -22,10 +22,15 @@ export type CookieAttributes = {
22
22
  sameSite?: true | false | 'lax' | 'strict' | 'none' | undefined;
23
23
  };
24
24
 
25
+ /**
26
+ * The interface for the MultisiteMiddleware configuration.
27
+ * @public
28
+ */
25
29
  export type MultisiteMiddlewareConfig = MiddlewareBaseConfig & SitecoreConfig['multisite'];
26
30
 
27
31
  /**
28
32
  * Middleware / handler for multisite support
33
+ * @public
29
34
  */
30
35
  export class MultisiteMiddleware extends MiddlewareBase {
31
36
  /**
@@ -66,8 +71,12 @@ export class MultisiteMiddleware extends MiddlewareBase {
66
71
 
67
72
  if (!isSitecorePreview) {
68
73
  if (!this.config.enabled) {
69
- debug.multisite('skipped (multisite middleware is disabled globally)');
70
- return next();
74
+ this.shouldWarnWhenDisabled(context);
75
+
76
+ if (this.shouldSkipWhenDisabled()) {
77
+ debug.multisite('skipped (multisite middleware is disabled globally)');
78
+ return next();
79
+ }
71
80
  }
72
81
 
73
82
  if (this.disabled(context)) {
@@ -133,6 +142,24 @@ export class MultisiteMiddleware extends MiddlewareBase {
133
142
  return context.url.pathname.includes('.') || super.disabled(context);
134
143
  }
135
144
 
145
+ /**
146
+ * Called when multisite is disabled. Override this method in subclasses to show router-specific warnings.
147
+ * @param {APIContext} context context
148
+ */
149
+ protected shouldWarnWhenDisabled(context: APIContext): void {
150
+ void context;
151
+ // Base implementation does nothing - subclasses can override to show warnings
152
+ }
153
+
154
+ /**
155
+ * Determines if middleware should be skipped when multisite is disabled.
156
+ * Override in subclasses to provide router-specific behavior.
157
+ * @returns {boolean} true if middleware should be skipped when disabled
158
+ */
159
+ protected shouldSkipWhenDisabled(): boolean {
160
+ return true; // Base class skips when disabled
161
+ }
162
+
136
163
  /**
137
164
  * Generates a site-specific rewrite path based on the provided pathname and site name.
138
165
  * @param {string} pathname - The pathname to be rewritten.
@@ -3,9 +3,12 @@ import { expect } from 'chai';
3
3
  import sinon from 'sinon';
4
4
  import sinonChai from 'sinon-chai';
5
5
  import { RobotsMiddleware } from './robots-middleware';
6
- import { SitecoreClient } from '@sitecore-content-sdk/core/client';
7
- import { SiteInfo } from '@sitecore-content-sdk/core/site';
8
- import { mockRequest } from '../test-data/helpers';
6
+ import { SitecoreClient } from '@sitecore-content-sdk/content/client';
7
+ import { SiteInfo } from '@sitecore-content-sdk/content/site';
8
+ import { mockRequest } from '../tests/helpers';
9
+ import { constants } from '@sitecore-content-sdk/core';
10
+
11
+ const { ERROR_MESSAGES } = constants;
9
12
 
10
13
  chai.use(sinonChai);
11
14
 
@@ -94,7 +97,7 @@ describe('RobotsMiddleware', () => {
94
97
 
95
98
  const body = await res.text();
96
99
  expect(res.status).to.equal(500);
97
- expect(body).to.deep.equal('Internal Server Error');
100
+ expect(body).to.deep.equal(`Internal Server Error. ${ERROR_MESSAGES.CONTACT_SUPPORT}`);
98
101
  });
99
102
 
100
103
  it('should use "localhost" as fallback when host header is missing', async () => {
@@ -110,4 +113,17 @@ describe('RobotsMiddleware', () => {
110
113
  expect(res.status).to.equal(200);
111
114
  expect(body).to.deep.equal('User-agent: *\nDisallow: /');
112
115
  });
116
+
117
+ it('should use x-forwarded-host header when present', async () => {
118
+ req = mockRequest({
119
+ headers: {
120
+ 'x-forwarded-host': 'proxy.forwarded.com',
121
+ host: 'localhost:3000',
122
+ },
123
+ });
124
+
125
+ await middleware.getHandler()(req as Request);
126
+
127
+ expect(siteResolverStub.getByHost).to.have.been.calledWith('proxy.forwarded.com');
128
+ });
113
129
  });
@@ -1,8 +1,12 @@
1
- import { SitecoreClient } from '@sitecore-content-sdk/core/client';
1
+ import { SitecoreClient } from '@sitecore-content-sdk/content/client';
2
+ import { constants } from '@sitecore-content-sdk/core';
2
3
  import { SiteInfo, SiteResolver } from '../site';
3
4
 
5
+ const { ERROR_MESSAGES } = constants;
6
+
4
7
  /**
5
8
  * Middleware for handling robots.txt requests.
9
+ * @public
6
10
  */
7
11
  export class RobotsMiddleware {
8
12
  private client: SitecoreClient;
@@ -21,7 +25,10 @@ export class RobotsMiddleware {
21
25
  const _res = new Response();
22
26
  _res.headers.append('content-type', 'text/plain');
23
27
 
24
- const hostName = _req.headers.get('host')?.split(':')[0] || 'localhost';
28
+ const hostName =
29
+ _req.headers.get('x-forwarded-host') ||
30
+ _req.headers.get('host')?.split(':')[0] ||
31
+ 'localhost';
25
32
  const site = this.siteResolver.getByHost(hostName);
26
33
 
27
34
  try {
@@ -38,7 +45,7 @@ export class RobotsMiddleware {
38
45
  headers: _res.headers,
39
46
  });
40
47
  } catch {
41
- return new Response('Internal Server Error', {
48
+ return new Response(`Internal Server Error. ${ERROR_MESSAGES.CONTACT_SUPPORT}`, {
42
49
  status: 500,
43
50
  headers: _res.headers,
44
51
  });
@@ -4,8 +4,11 @@ import { expect } from 'chai';
4
4
  import sinon from 'sinon';
5
5
  import sinonChai from 'sinon-chai';
6
6
  import { SitemapMiddleware } from './sitemap-middleware';
7
- import { SitecoreClient } from '@sitecore-content-sdk/core/client';
8
- import { mockRequest } from '../test-data/helpers';
7
+ import { SitecoreClient } from '@sitecore-content-sdk/content/client';
8
+ import { constants } from '@sitecore-content-sdk/core';
9
+ import { mockRequest } from '../tests/helpers';
10
+
11
+ const { ERROR_MESSAGES } = constants;
9
12
 
10
13
  chai.use(sinonChai);
11
14
 
@@ -123,6 +126,35 @@ describe('SitemapMiddleware', () => {
123
126
  });
124
127
  });
125
128
 
129
+ it('should use x-forwarded-host header when present', async () => {
130
+ req = mockRequest({
131
+ headers: {
132
+ 'x-forwarded-host': 'example.com',
133
+ host: 'localhost:3000',
134
+ },
135
+ });
136
+
137
+ await middleware.getHandler()(req as Request);
138
+
139
+ expect(siteResolverStub.getByHost).to.have.been.calledWith('example.com');
140
+ });
141
+
142
+ it('should use empty string when both x-forwarded-host and host headers are missing', async () => {
143
+ req.headers?.delete('host');
144
+ const xmlContent = '<sitemapindex>...</sitemapindex>';
145
+
146
+ siteResolverStub.getByHost.withArgs('').returns(sites[1]);
147
+
148
+ sitecoreClientStub.getSiteMap.resolves(xmlContent);
149
+
150
+ await middleware.getHandler()(req as Request);
151
+
152
+ expect(sitecoreClientStub.getSiteMap.firstCall.args[0]).to.deep.include({
153
+ reqHost: '',
154
+ reqProtocol: 'https',
155
+ });
156
+ });
157
+
126
158
  it('should redirect to 404 when REDIRECT_404 error is thrown', async () => {
127
159
  const error = new Error('REDIRECT_404');
128
160
 
@@ -146,7 +178,7 @@ describe('SitemapMiddleware', () => {
146
178
 
147
179
  const body = await res.text();
148
180
  expect(res.status).to.equal(500);
149
- expect(body).to.equal('Internal Server Error');
181
+ expect(body).to.equal(`Internal Server Error. ${ERROR_MESSAGES.CONTACT_SUPPORT}`);
150
182
  });
151
183
  });
152
184
  });
@@ -1,12 +1,13 @@
1
- import {
2
- SitecoreClient,
3
- SitemapXmlOptions,
4
- } from '@sitecore-content-sdk/core/client';
1
+ import { SitecoreClient, SitemapXmlOptions } from '@sitecore-content-sdk/content/client';
2
+ import { constants } from '@sitecore-content-sdk/core';
5
3
  import { SiteInfo, SiteResolver } from '../site';
6
4
 
5
+ const { ERROR_MESSAGES } = constants;
6
+
7
7
  /**
8
8
  * Middleware for handling sitemap requests.
9
9
  * Encapsulates all HTTP-related logic for sitemap generation and delivery.
10
+ * @public
10
11
  */
11
12
  export class SitemapMiddleware {
12
13
  private client: SitecoreClient;
@@ -28,7 +29,7 @@ export class SitemapMiddleware {
28
29
  const idMatch = segments[1].match(/(\d+)(?=\.xml$)/);
29
30
  const id = idMatch ? idMatch[1] : '';
30
31
 
31
- const reqHost = _req.headers.get('host') || '';
32
+ const reqHost = _req.headers.get('x-forwarded-host') || _req.headers.get('host') || '';
32
33
  const reqProtocol = _req.headers.get('x-forwarded-proto') || 'https';
33
34
  const site = this.siteResolver.getByHost(reqHost);
34
35
 
@@ -56,7 +57,7 @@ export class SitemapMiddleware {
56
57
  },
57
58
  });
58
59
  } else {
59
- return new Response('Internal Server Error', {
60
+ return new Response(`Internal Server Error. ${ERROR_MESSAGES.CONTACT_SUPPORT}`, {
60
61
  status: 500,
61
62
  });
62
63
  }
@@ -3,7 +3,7 @@ import {
3
3
  LayoutServiceData,
4
4
  ComponentRendering,
5
5
  PlaceholdersData,
6
- } from '@sitecore-content-sdk/core/layout';
6
+ } from '@sitecore-content-sdk/content/layout';
7
7
  import {
8
8
  AstroContentSdkComponent,
9
9
  ComponentMap,
@@ -23,6 +23,10 @@ export type ComponentPropsRequest = {
23
23
  // context: NextContext;
24
24
  };
25
25
 
26
+ /**
27
+ * The service for fetching component props.
28
+ * @public
29
+ */
26
30
  export class ComponentPropsService {
27
31
  async fetchComponentProps(
28
32
  params: FetchComponentPropsArguments
@@ -67,7 +71,7 @@ export class ComponentPropsService {
67
71
  // const fetchFunc = (await this.getModule(components, r.componentName))
68
72
  // ?.getComponentServerProps;
69
73
 
70
- const fetchFunc = ""; // getModule
74
+ const fetchFunc = ''; // getModule
71
75
 
72
76
  if (fetchFunc) {
73
77
  params.requests &&
@@ -113,7 +117,6 @@ export class ComponentPropsService {
113
117
  return;
114
118
  }
115
119
 
116
-
117
120
  // return req
118
121
  // .fetch(req.rendering, req.layoutData /*, req.context*/)
119
122
  // .then((result) => {
@@ -153,9 +156,7 @@ export class ComponentPropsService {
153
156
  * @param {PlaceholdersData} placeholders placeholders
154
157
  * @returns {ComponentRendering[]} renderings
155
158
  */
156
- protected flatRenderings(
157
- placeholders: PlaceholdersData
158
- ): ComponentRendering[] {
159
+ protected flatRenderings(placeholders: PlaceholdersData): ComponentRendering[] {
159
160
  const allComponentRenderings: ComponentRendering[] = [];
160
161
  const placeholdersArr = Object.values(placeholders);
161
162
 
@@ -2,16 +2,27 @@ export type ComponentPropsError = { error: string; componentName: string };
2
2
 
3
3
  /**
4
4
  * Shape of component props storage
5
+ * @public
5
6
  */
6
7
  export type ComponentPropsCollection = {
7
8
  [componentUid: string]: unknown | ComponentPropsError;
8
9
  };
9
10
 
11
+ /**
12
+ * Represents an Astro component import
13
+ * @public
14
+ */
10
15
  export type AstroContentSdkComponent = (_props: Record<string, any>) => any;
11
16
 
17
+ /**
18
+ * Represents PreviewData type
19
+ * @public
20
+ */
12
21
  export type PreviewData = string | false | object | undefined;
13
22
 
14
- export type ComponentMap<
15
- TComponent extends AstroContentSdkComponent = AstroContentSdkComponent
16
- > = Map<string, TComponent>;
17
-
23
+ /**
24
+ * Represents component map type
25
+ * @public
26
+ */
27
+ export type ComponentMap<TComponent extends AstroContentSdkComponent = AstroContentSdkComponent> =
28
+ Map<string, TComponent>;
package/src/site/index.ts CHANGED
@@ -1 +1 @@
1
- export { SiteResolver, SiteInfo } from '@sitecore-content-sdk/core/site';
1
+ export { SiteResolver, SiteInfo, SITE_PREFIX } from '@sitecore-content-sdk/content/site';
@@ -0,0 +1,61 @@
1
+ import type { ComponentProps } from 'astro/types';
2
+ import {
3
+ experimental_AstroContainer as AstroContainer,
4
+ type ContainerRenderOptions,
5
+ } from 'astro/container';
6
+ import { Page } from '@sitecore-content-sdk/content/client';
7
+ import { ComponentMap } from '../sharedTypes/component-props';
8
+ import { vi } from 'vitest';
9
+ import { SitecoreContext } from '../context';
10
+
11
+ type AstroComponentFactory = Parameters<AstroContainer['renderToString']>[0];
12
+
13
+ type ComponentContainerRenderOptions<T extends AstroComponentFactory> = Omit<
14
+ ContainerRenderOptions,
15
+ 'props'
16
+ > & {
17
+ // @ts-expect-error
18
+ props?: ComponentProps<T>;
19
+ };
20
+
21
+ /**
22
+ * Renders an Astro component to a DOM element for tests.
23
+ * @param {AstroComponentFactory} Component - The Astro component to render.
24
+ * @param {ComponentContainerRenderOptions<T>} options - Container render options, including component props.
25
+ * @returns A `div` whose `innerHTML` is the rendered markup (Astro attributes stripped).
26
+ */
27
+ export async function renderAstroComponent<T extends AstroComponentFactory>(
28
+ Component: T,
29
+ options: ComponentContainerRenderOptions<T> = {}
30
+ ) {
31
+ const container = await AstroContainer.create();
32
+ const result = await container.renderToString(Component, options);
33
+
34
+ const html = removeAstroAttributes(result);
35
+
36
+ const div = document.createElement('div');
37
+ div.innerHTML = html;
38
+
39
+ return div;
40
+ }
41
+
42
+ export const mockSitecoreContext = (page?: Page, map?: ComponentMap, api?: any) => {
43
+ const getMock = vi.fn();
44
+
45
+ getMock.mockReturnValue({
46
+ page: page,
47
+ componentMap: map,
48
+ api: api,
49
+ });
50
+
51
+ SitecoreContext.get = getMock;
52
+ };
53
+
54
+ /**
55
+ * Removes Astro-specific data attributes (data-astro-*)
56
+ * and whitespaces between HTML tags from a given HTML string.
57
+ * @param {string} html - Raw HTML string from Astro rendering.
58
+ */
59
+ const removeAstroAttributes = (html: string) => {
60
+ return html.replace(/\s*data-astro-[a-zA-Z0-9-]+=(\"|\').*?\1\s*/g, '').replace(/>\s+</g, '><');
61
+ };
@@ -0,0 +1,3 @@
1
+ ---
2
+ ---
3
+ <div class="custom-error">This is a custom error component!</div>
@@ -0,0 +1,10 @@
1
+ ---
2
+ import HiddenRendering from '../../components/HiddenRendering.astro';
3
+
4
+ const props = Astro.props;
5
+ ---
6
+
7
+ <div class="hidden-rendering">
8
+ <HiddenRendering />
9
+ <p>{props.rendering.componentName}</p>
10
+ </div>
@@ -0,0 +1,9 @@
1
+ ---
2
+ import MissingComponent from '../../components/MissingComponent.astro';
3
+
4
+ const props = Astro.props;
5
+ ---
6
+
7
+ <div class="missing-component">
8
+ <MissingComponent {...props} />
9
+ </div>
@@ -0,0 +1,12 @@
1
+ ---
2
+ const props = Astro.props as {
3
+ [prop: string]: unknown;
4
+ fields?: { message?: { value?: string } };
5
+ extraDiv?: boolean;
6
+ };
7
+ ---
8
+
9
+ <div class="download-callout-mock">
10
+ {props.fields?.message ? props.fields.message.value : ''}
11
+ {props.extraDiv ? <div class="extra">extra!</div> : null}
12
+ </div>
@@ -0,0 +1,5 @@
1
+ ---
2
+
3
+ ---
4
+
5
+ <span class="empty-field-value-placeholder">Custom Empty field value</span>
@@ -0,0 +1,10 @@
1
+ ---
2
+ import ErrorBoundary from '../../components/ErrorBoundary.astro';
3
+ import TestComponentWithError from './TestComponentWithError.astro';
4
+
5
+ const props = Astro.props;
6
+
7
+ ---
8
+ <ErrorBoundary {...props}>
9
+ <TestComponentWithError />
10
+ </ErrorBoundary>