import { expect, fixture, html } from '@open-wc/testing'; import './nile-qr-code'; import NileQrCode from './nile-qr-code'; describe('NileQrCode', () => { // === RENDERING === it('1. should render without errors', async () => { const el = await fixture(html``); expect(el).to.exist; }); it('2. should have a shadow root', async () => { const el = await fixture(html``); expect(el.shadowRoot).to.not.be.null; }); it('3. should render a canvas element', async () => { const el = await fixture(html``); const canvas = el.shadowRoot!.querySelector('canvas'); expect(canvas).to.exist; }); it('4. should have a canvas with correct default size', async () => { const el = await fixture(html``); const canvas = el.shadowRoot!.querySelector('canvas'); expect(canvas!.getAttribute('width')).to.equal('128'); expect(canvas!.getAttribute('height')).to.equal('128'); }); it('5. shadow root should be open mode', async () => { const el = await fixture(html``); expect(el.shadowRoot!.mode).to.equal('open'); }); // === DEFAULT PROPERTY VALUES === it('6. should have default value of empty string', async () => { const el = await fixture(html``); expect(el.value).to.equal(''); }); it('7. should have default size of 128', async () => { const el = await fixture(html``); expect(el.size).to.equal(128); }); it('8. should have default fill of black', async () => { const el = await fixture(html``); expect(el.fill).to.equal('black'); }); it('9. should have default background of white', async () => { const el = await fixture(html``); expect(el.background).to.equal('white'); }); it('10. should have default radius of 0', async () => { const el = await fixture(html``); expect(el.radius).to.equal(0); }); it('11. should have default errorCorrection of H', async () => { const el = await fixture(html``); expect(el.errorCorrection).to.equal('H'); }); it('12. should have default label of empty string', async () => { const el = await fixture(html``); expect(el.label).to.equal(''); }); // === PROPERTY SETTING === it('13. should accept value property', async () => { const el = await fixture(html``); expect(el.value).to.equal('https://example.com'); }); it('14. should accept size property', async () => { const el = await fixture(html``); expect(el.size).to.equal(256); }); it('15. should accept fill property', async () => { const el = await fixture(html``); expect(el.fill).to.equal('red'); }); it('16. should accept background property', async () => { const el = await fixture(html``); expect(el.background).to.equal('blue'); }); it('17. should accept radius property', async () => { const el = await fixture(html``); expect(el.radius).to.equal(0.5); }); it('18. should accept error-correction property', async () => { const el = await fixture(html``); expect(el.errorCorrection).to.equal('L'); }); it('19. should accept label property', async () => { const el = await fixture(html``); expect(el.label).to.equal('Scan me'); }); // === ATTRIBUTE REFLECTION === it('20. should reflect value to attribute', async () => { const el = await fixture(html``); expect(el.getAttribute('value')).to.equal('test'); }); it('21. should reflect size to attribute', async () => { const el = await fixture(html``); expect(el.getAttribute('size')).to.equal('64'); }); it('22. should reflect fill to attribute', async () => { const el = await fixture(html``); expect(el.getAttribute('fill')).to.equal('green'); }); it('23. should reflect background to attribute', async () => { const el = await fixture(html``); expect(el.getAttribute('background')).to.equal('yellow'); }); it('24. should reflect radius to attribute', async () => { const el = await fixture(html``); expect(el.getAttribute('radius')).to.equal('0.3'); }); it('25. should reflect error-correction to attribute', async () => { const el = await fixture(html``); expect(el.getAttribute('error-correction')).to.equal('M'); }); it('26. should reflect label to attribute', async () => { const el = await fixture(html``); expect(el.getAttribute('label')).to.equal('My QR'); }); // === ACCESSIBILITY === it('27. should have role=img on canvas', async () => { const el = await fixture(html``); const canvas = el.shadowRoot!.querySelector('canvas'); expect(canvas!.getAttribute('role')).to.equal('img'); }); it('28. should use label for aria-label when provided', async () => { const el = await fixture(html``); const canvas = el.shadowRoot!.querySelector('canvas'); expect(canvas!.getAttribute('aria-label')).to.equal('Scan this code'); }); it('29. should fallback to value for aria-label when no label', async () => { const el = await fixture(html``); const canvas = el.shadowRoot!.querySelector('canvas'); expect(canvas!.getAttribute('aria-label')).to.equal('https://test.com'); }); it('30. should fallback to "QR Code" for aria-label when no label and no value', async () => { const el = await fixture(html``); const canvas = el.shadowRoot!.querySelector('canvas'); expect(canvas!.getAttribute('aria-label')).to.equal('QR Code'); }); // === CSS PARTS === it('31. should have base part on canvas', async () => { const el = await fixture(html``); const canvas = el.shadowRoot!.querySelector('[part="base"]'); expect(canvas).to.exist; }); it('32. base part should be the canvas element', async () => { const el = await fixture(html``); const canvas = el.shadowRoot!.querySelector('[part="base"]'); expect(canvas!.tagName.toLowerCase()).to.equal('canvas'); }); // === SIZE CHANGES === it('33. should update canvas width when size changes', async () => { const el = await fixture(html``); const canvas = el.shadowRoot!.querySelector('canvas'); expect(canvas!.getAttribute('width')).to.equal('64'); }); it('34. should update canvas height when size changes', async () => { const el = await fixture(html``); const canvas = el.shadowRoot!.querySelector('canvas'); expect(canvas!.getAttribute('height')).to.equal('256'); }); it('35. should redraw when size property is changed programmatically', async () => { const el = await fixture(html``); el.size = 64; await el.updateComplete; const canvas = el.shadowRoot!.querySelector('canvas'); expect(canvas!.getAttribute('width')).to.equal('64'); }); // === VALUE CHANGES === it('36. should render QR code when value is set', async () => { const el = await fixture(html``); const canvas = el.shadowRoot!.querySelector('canvas'); expect(canvas).to.exist; // Canvas should have some drawn content const ctx = canvas!.getContext('2d'); expect(ctx).to.exist; }); it('37. should redraw when value changes programmatically', async () => { const el = await fixture(html``); el.value = 'Second'; await el.updateComplete; expect(el.value).to.equal('Second'); }); it('38. should handle empty value gracefully', async () => { const el = await fixture(html``); expect(el).to.exist; expect(el.shadowRoot!.querySelector('canvas')).to.exist; }); it('39. should handle long URLs', async () => { const longUrl = 'https://example.com/' + 'a'.repeat(200); const el = await fixture(html``); expect(el).to.exist; expect(el.value).to.equal(longUrl); }); // === ERROR CORRECTION LEVELS === it('40. should accept error-correction L', async () => { const el = await fixture(html``); expect(el.errorCorrection).to.equal('L'); }); it('41. should accept error-correction M', async () => { const el = await fixture(html``); expect(el.errorCorrection).to.equal('M'); }); it('42. should accept error-correction Q', async () => { const el = await fixture(html``); expect(el.errorCorrection).to.equal('Q'); }); it('43. should accept error-correction H', async () => { const el = await fixture(html``); expect(el.errorCorrection).to.equal('H'); }); // === RADIUS === it('44. should accept radius 0', async () => { const el = await fixture(html``); expect(el.radius).to.equal(0); }); it('45. should accept radius 0.5', async () => { const el = await fixture(html``); expect(el.radius).to.equal(0.5); }); it('46. should accept fractional radius values', async () => { const el = await fixture(html``); expect(el.radius).to.equal(0.25); }); // === COLOR PROPERTIES === it('47. should accept hex fill color', async () => { const el = await fixture(html``); expect(el.fill).to.equal('#FF0000'); }); it('48. should accept rgb fill color', async () => { const el = await fixture(html``); expect(el.fill).to.equal('rgb(255,0,0)'); }); it('49. should accept transparent background', async () => { const el = await fixture(html``); expect(el.background).to.equal('transparent'); }); it('50. should accept hex background color', async () => { const el = await fixture(html``); expect(el.background).to.equal('#0000FF'); }); // === INSTANCE === it('51. should be instance of NileQrCode', async () => { const el = await fixture(html``); expect(el).to.be.instanceOf(NileQrCode); }); it('52. should have correct tag name', async () => { const el = await fixture(html``); expect(el.tagName.toLowerCase()).to.equal('nile-qr-code'); }); it('53. should be a defined custom element', async () => { expect(customElements.get('nile-qr-code')).to.exist; }); it('54. should be registered as NileQrCode', async () => { expect(customElements.get('nile-qr-code')).to.equal(NileQrCode); }); // === STATIC STYLES === it('55. should have static styles', async () => { const styles = NileQrCode.styles; expect(styles).to.exist; expect(Array.isArray(styles)).to.be.true; }); it('56. styles array should have at least one entry', async () => { const styles = NileQrCode.styles; expect((styles as any[]).length).to.be.greaterThan(0); }); // === RENDER METHOD === it('57. should have render method', async () => { const el = await fixture(html``); expect(el.render).to.be.a('function'); }); it('58. render should return a TemplateResult', async () => { const el = await fixture(html``); const result = el.render(); expect(result).to.exist; }); it('59. should not throw when render is called', async () => { const el = await fixture(html``); expect(() => el.render()).to.not.throw(); }); // === UPDATE LIFECYCLE === it('60. should have updateComplete promise', async () => { const el = await fixture(html``); expect(el.updateComplete).to.be.a('promise'); }); it('61. updateComplete should resolve', async () => { const el = await fixture(html``); const result = await el.updateComplete; expect(result).to.not.be.undefined; }); it('62. should render consistently after re-render', async () => { const el = await fixture(html``); await el.requestUpdate(); await el.updateComplete; const canvas = el.shadowRoot!.querySelector('canvas'); expect(canvas).to.exist; }); it('63. should maintain canvas after multiple updates', async () => { const el = await fixture(html``); for (let i = 0; i < 5; i++) { await el.requestUpdate(); await el.updateComplete; } const canvas = el.shadowRoot!.querySelector('canvas'); expect(canvas).to.exist; }); // === DOM STRUCTURE === it('64. canvas should be the only child in shadow root template', async () => { const el = await fixture(html``); const canvases = el.shadowRoot!.querySelectorAll('canvas'); expect(canvases.length).to.equal(1); }); it('65. should not have any slot elements', async () => { const el = await fixture(html``); const slots = el.shadowRoot!.querySelectorAll('slot'); expect(slots.length).to.equal(0); }); it('66. should not have any input elements', async () => { const el = await fixture(html``); const inputs = el.shadowRoot!.querySelectorAll('input'); expect(inputs.length).to.equal(0); }); it('67. should not have any button elements', async () => { const el = await fixture(html``); const buttons = el.shadowRoot!.querySelectorAll('button'); expect(buttons.length).to.equal(0); }); it('68. should not have any SVG elements', async () => { const el = await fixture(html``); const svgs = el.shadowRoot!.querySelectorAll('svg'); expect(svgs.length).to.equal(0); }); it('69. should not have light DOM content', async () => { const el = await fixture(html``); expect(el.childNodes.length).to.equal(0); }); // === DOM MANIPULATION === it('70. should render after being added to DOM programmatically', async () => { const container = await fixture(html`
`); const el = document.createElement('nile-qr-code') as NileQrCode; container.appendChild(el); await el.updateComplete; expect(el.shadowRoot!.querySelector('canvas')).to.exist; }); it('71. should handle removal and re-addition', async () => { const container = await fixture(html`
`); const el = document.createElement('nile-qr-code') as NileQrCode; container.appendChild(el); await el.updateComplete; container.removeChild(el); container.appendChild(el); await el.updateComplete; expect(el.shadowRoot!.querySelector('canvas')).to.exist; }); it('72. should work when created via new', async () => { const el = new NileQrCode(); document.body.appendChild(el); await el.updateComplete; expect(el.shadowRoot!.querySelector('canvas')).to.exist; document.body.removeChild(el); }); it('73. should be connectedCallback-compatible', async () => { const el = document.createElement('nile-qr-code') as NileQrCode; document.body.appendChild(el); await el.updateComplete; expect(el.isConnected).to.be.true; document.body.removeChild(el); }); it('74. should handle disconnectedCallback', async () => { const el = await fixture(html``); el.remove(); expect(el.isConnected).to.be.false; }); // === RENDERING CONTEXTS === it('75. should render correctly inside a flex container', async () => { const container = await fixture(html`
`); const qr = container.querySelector('nile-qr-code')!; expect((qr as NileQrCode).shadowRoot!.querySelector('canvas')).to.exist; }); it('76. should render correctly inside a grid container', async () => { const container = await fixture(html`
`); const qr = container.querySelector('nile-qr-code')!; expect((qr as NileQrCode).shadowRoot!.querySelector('canvas')).to.exist; }); it('77. should render correctly with class attribute', async () => { const el = await fixture(html``); expect(el.classList.contains('custom-class')).to.be.true; }); it('78. should render correctly with id attribute', async () => { const el = await fixture(html``); expect(el.id).to.equal('my-qr'); }); it('79. should render correctly with style attribute', async () => { const el = await fixture(html``); expect(el.style.margin).to.equal('10px'); }); it('80. should render correctly with hidden attribute', async () => { const el = await fixture(html``); expect(el.hidden).to.be.true; }); // === MULTIPLE INSTANCES === it('81. should render multiple QR codes independently', async () => { const container = await fixture(html`
`); const qrCodes = container.querySelectorAll('nile-qr-code'); expect(qrCodes.length).to.equal(3); }); it('82. each QR code should have its own shadow root', async () => { const container = await fixture(html`
`); const qrCodes = container.querySelectorAll('nile-qr-code'); expect(qrCodes[0].shadowRoot).to.not.equal(qrCodes[1].shadowRoot); }); it('83. multiple instances should each have a canvas', async () => { const container = await fixture(html`
`); const qrCodes = container.querySelectorAll('nile-qr-code'); qrCodes.forEach(qr => { expect((qr as NileQrCode).shadowRoot!.querySelector('canvas')).to.exist; }); }); // === PROGRAMMATIC PROPERTY CHANGES === it('84. should redraw when fill changes programmatically', async () => { const el = await fixture(html``); el.fill = 'red'; await el.updateComplete; expect(el.fill).to.equal('red'); }); it('85. should redraw when background changes programmatically', async () => { const el = await fixture(html``); el.background = 'transparent'; await el.updateComplete; expect(el.background).to.equal('transparent'); }); it('86. should redraw when radius changes programmatically', async () => { const el = await fixture(html``); el.radius = 0.3; await el.updateComplete; expect(el.radius).to.equal(0.3); }); it('87. should redraw when errorCorrection changes programmatically', async () => { const el = await fixture(html``); el.errorCorrection = 'L'; await el.updateComplete; expect(el.errorCorrection).to.equal('L'); }); // === EDGE CASES === it('88. should handle being moved between containers', async () => { const container1 = await fixture(html`
`) as HTMLElement; const container2 = await fixture(html`
`) as HTMLElement; const el = document.createElement('nile-qr-code') as NileQrCode; container1.appendChild(el); await el.updateComplete; container2.appendChild(el); await el.updateComplete; expect(el.shadowRoot!.querySelector('canvas')).to.exist; }); it('89. should handle rapid add/remove', async () => { const container = await fixture(html`
`) as HTMLElement; const el = document.createElement('nile-qr-code') as NileQrCode; container.appendChild(el); container.removeChild(el); container.appendChild(el); await el.updateComplete; expect(el.shadowRoot!.querySelector('canvas')).to.exist; }); it('90. shadowRoot host should reference the element', async () => { const el = await fixture(html``); expect(el.shadowRoot!.host).to.equal(el); }); it('91. should handle data-* attributes gracefully', async () => { const el = await fixture(html``); expect(el.getAttribute('data-testid')).to.equal('qr1'); }); it('92. should handle setting custom aria attributes', async () => { const el = await fixture(html``); expect(el.getAttribute('aria-hidden')).to.equal('true'); }); // === CANVAS ATTRIBUTES === it('93. canvas should have width attribute matching size', async () => { const el = await fixture(html``); const canvas = el.shadowRoot!.querySelector('canvas'); expect(canvas!.getAttribute('width')).to.equal('200'); }); it('94. canvas should have height attribute matching size', async () => { const el = await fixture(html``); const canvas = el.shadowRoot!.querySelector('canvas'); expect(canvas!.getAttribute('height')).to.equal('200'); }); it('95. canvas should have part attribute', async () => { const el = await fixture(html``); const canvas = el.shadowRoot!.querySelector('canvas'); expect(canvas!.getAttribute('part')).to.equal('base'); }); it('96. canvas should have role attribute', async () => { const el = await fixture(html``); const canvas = el.shadowRoot!.querySelector('canvas'); expect(canvas!.hasAttribute('role')).to.be.true; }); it('97. canvas should have aria-label attribute', async () => { const el = await fixture(html``); const canvas = el.shadowRoot!.querySelector('canvas'); expect(canvas!.hasAttribute('aria-label')).to.be.true; }); // === CONNECTED STATE === it('98. should be in connected state after fixture', async () => { const el = await fixture(html``); expect(el.isConnected).to.be.true; }); it('99. should not affect parent container', async () => { const container = await fixture(html`
`) as HTMLElement; expect(container.classList.contains('parent')).to.be.true; expect(container.children.length).to.equal(1); }); it('100. should handle all properties at once', async () => { const el = await fixture(html` `); expect(el.value).to.equal('https://example.com'); expect(el.size).to.equal(256); expect(el.fill).to.equal('#333'); expect(el.background).to.equal('#FFF'); expect(el.radius).to.equal(0.4); expect(el.errorCorrection).to.equal('Q'); expect(el.label).to.equal('Scan to visit'); const canvas = el.shadowRoot!.querySelector('canvas'); expect(canvas).to.exist; expect(canvas!.getAttribute('aria-label')).to.equal('Scan to visit'); }); // === IMAGE OVERLAY DEFAULTS === it('101. should have default image of empty string', async () => { const el = await fixture(html``); expect(el.image).to.equal(''); }); it('102. should have default imageSize of 0.25', async () => { const el = await fixture(html``); expect(el.imageSize).to.equal(0.25); }); it('103. should have default imagePadding of 4', async () => { const el = await fixture(html``); expect(el.imagePadding).to.equal(4); }); it('104. should have default imageRadius of 4', async () => { const el = await fixture(html``); expect(el.imageRadius).to.equal(4); }); // === IMAGE OVERLAY PROPERTY SETTING === it('105. should accept image property', async () => { const el = await fixture(html``); expect(el.image).to.equal('https://example.com/logo.png'); }); it('106. should accept image-size property', async () => { const el = await fixture(html``); expect(el.imageSize).to.equal(0.3); }); it('107. should accept image-padding property', async () => { const el = await fixture(html``); expect(el.imagePadding).to.equal(8); }); it('108. should accept image-radius property', async () => { const el = await fixture(html``); expect(el.imageRadius).to.equal(12); }); // === IMAGE OVERLAY ATTRIBUTE REFLECTION === it('109. should reflect image to attribute', async () => { const el = await fixture(html``); expect(el.getAttribute('image')).to.equal('logo.png'); }); it('110. should reflect image-size to attribute', async () => { const el = await fixture(html``); expect(el.getAttribute('image-size')).to.equal('0.35'); }); it('111. should reflect image-padding to attribute', async () => { const el = await fixture(html``); expect(el.getAttribute('image-padding')).to.equal('6'); }); it('112. should reflect image-radius to attribute', async () => { const el = await fixture(html``); expect(el.getAttribute('image-radius')).to.equal('10'); }); // === IMAGE OVERLAY RENDERING === it('113. should still render canvas when image is set', async () => { const el = await fixture(html``); expect(el.shadowRoot!.querySelector('canvas')).to.exist; }); it('114. should handle invalid image URL gracefully', async () => { const el = await fixture(html``); await el.updateComplete; expect(el).to.exist; expect(el.shadowRoot!.querySelector('canvas')).to.exist; }); it('115. should handle image property change programmatically', async () => { const el = await fixture(html``); el.image = 'https://example.com/logo.png'; await el.updateComplete; expect(el.image).to.equal('https://example.com/logo.png'); }); it('116. should clear image when set to empty string', async () => { const el = await fixture(html``); el.image = ''; await el.updateComplete; expect(el.image).to.equal(''); }); // === GRADIENT FILL DEFAULTS === it('117. should have default fillGradient of empty string', async () => { const el = await fixture(html``); expect(el.fillGradient).to.equal(''); }); // === GRADIENT FILL PROPERTY SETTING === it('118. should accept fill-gradient property', async () => { const el = await fixture(html``); expect(el.fillGradient).to.equal('135, #6366f1, #ec4899'); }); it('119. should reflect fill-gradient to attribute', async () => { const el = await fixture(html``); expect(el.getAttribute('fill-gradient')).to.equal('90, red, blue'); }); it('120. should render with gradient fill', async () => { const el = await fixture(html``); expect(el.shadowRoot!.querySelector('canvas')).to.exist; }); it('121. should fallback to fill when gradient is malformed', async () => { const el = await fixture(html``); await el.updateComplete; expect(el).to.exist; expect(el.shadowRoot!.querySelector('canvas')).to.exist; }); it('122. should handle gradient with multiple color stops', async () => { const el = await fixture(html``); await el.updateComplete; expect(el).to.exist; }); it('123. should handle gradient change programmatically', async () => { const el = await fixture(html``); el.fillGradient = '90, #333, #999'; await el.updateComplete; expect(el.fillGradient).to.equal('90, #333, #999'); }); // === DOWNLOAD METHOD === it('124. should have a download method', async () => { const el = await fixture(html``); expect(el.download).to.be.a('function'); }); it('125. should have a toDataURL method', async () => { const el = await fixture(html``); expect(el.toDataURL).to.be.a('function'); }); it('126. toDataURL should return a data URL string', async () => { const el = await fixture(html``); const dataUrl = el.toDataURL(); expect(dataUrl).to.be.a('string'); expect(dataUrl).to.include('data:image/png'); }); it('127. toDataURL should accept a custom MIME type', async () => { const el = await fixture(html``); const dataUrl = el.toDataURL('image/jpeg'); expect(dataUrl).to.be.a('string'); }); it('128. download should not throw', async () => { const el = await fixture(html``); expect(() => el.download('test.png')).to.not.throw(); }); // === COMBINED FEATURES === it('129. should handle gradient + rounded modules together', async () => { const el = await fixture(html` `); await el.updateComplete; expect(el.shadowRoot!.querySelector('canvas')).to.exist; }); it('130. should handle all new properties at once', async () => { const el = await fixture(html` `); expect(el.value).to.equal('https://example.com'); expect(el.size).to.equal(256); expect(el.imageSize).to.equal(0.25); expect(el.imagePadding).to.equal(6); expect(el.imageRadius).to.equal(8); expect(el.fillGradient).to.equal('135, #6366f1, #ec4899'); expect(el.shadowRoot!.querySelector('canvas')).to.exist; }); });