diff --git a/packages/@lwc/ssr-client-utils/src/__tests__/index.spec.ts b/packages/@lwc/ssr-client-utils/src/__tests__/index.spec.ts new file mode 100644 index 0000000000..7487c5ed61 --- /dev/null +++ b/packages/@lwc/ssr-client-utils/src/__tests__/index.spec.ts @@ -0,0 +1,364 @@ +/* + * Copyright (c) 2025, salesforce.com, inc. + * All rights reserved. + * SPDX-License-Identifier: MIT + * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT + */ + +import { describe, it, expect, beforeEach, vi } from 'vitest'; + +// Mock CSS API for testing +const mockCSSStyleSheet = vi.fn(); +global.CSSStyleSheet = mockCSSStyleSheet; + +// Mock customElements API +const mockDefine = vi.fn(); +global.customElements = { + define: mockDefine, +} as any; + +// Mock document.createElement +global.document = { + createElement: vi.fn(), +} as any; + +describe('registerLwcStyleComponent', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should register lwc-style custom element', async () => { + const { registerLwcStyleComponent } = await import('../index'); + registerLwcStyleComponent(); + expect(mockDefine).toHaveBeenCalledWith('lwc-style', expect.any(Function)); + }); +}); + +describe('StyleDeduplicator (lwc-style element)', () => { + let StyleDeduplicatorClass: any; + + beforeEach(async () => { + vi.clearAllMocks(); + mockDefine.mockClear(); + mockCSSStyleSheet.mockClear(); + + // Clear modules to get fresh import + vi.resetModules(); + + // Set up test environment for cache clearing + const originalProcess = (global as any).process; + (global as any).process = { env: { NODE_ENV: 'test-karma-lwc' } }; + (global as any).window = {}; + + // Import fresh module + const { registerLwcStyleComponent } = await import('../index'); + registerLwcStyleComponent(); + + // Get the StyleDeduplicator class + StyleDeduplicatorClass = mockDefine.mock.calls[0][1]; + + // Clear the cache + if ((global as any).window?.__lwcClearStylesheetCache) { + (global as any).window.__lwcClearStylesheetCache(); + } + + // Restore process + (global as any).process = originalProcess; + }); + + describe('connectedCallback', () => { + it('should throw error when style-id attribute is missing', () => { + const mockElement = { + getAttribute: vi.fn().mockReturnValue(null), + }; + + expect(() => { + StyleDeduplicatorClass.prototype.connectedCallback.call(mockElement); + }).toThrow('"style-id" attribute must be supplied for element'); + }); + + it('should throw error when style-id attribute is empty string', () => { + const mockElement = { + getAttribute: vi.fn().mockReturnValue(''), + }; + + expect(() => { + StyleDeduplicatorClass.prototype.connectedCallback.call(mockElement); + }).toThrow('"style-id" attribute must be supplied for element'); + }); + + it('should throw error when referenced style element does not exist', () => { + const mockElement = { + getAttribute: vi.fn().mockReturnValue('test-style-id'), + getRootNode: vi.fn().mockReturnValue({ + getElementById: vi.fn().mockReturnValue(null), + }), + }; + + expect(() => { + StyleDeduplicatorClass.prototype.connectedCallback.call(mockElement); + }).toThrow( + ' tag found with no corresponding