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;
});
});