diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..0b27943e --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +test/cases/tsx/src/counter.tsx diff --git a/docs/components/banner-cta/banner-cta.test.ts b/docs/components/banner-cta/banner-cta.test.ts index 4ca1ecdd..6767bb35 100644 --- a/docs/components/banner-cta/banner-cta.test.ts +++ b/docs/components/banner-cta/banner-cta.test.ts @@ -43,6 +43,7 @@ describe('Components/Banner CTA', () => { afterEach(() => { bannerCta.remove(); + // @ts-expect-error bannerCta = undefined; }); }); diff --git a/docs/components/banner-splash/banner-splash.test.ts b/docs/components/banner-splash/banner-splash.test.ts index b5ad7a2b..c414b5b8 100644 --- a/docs/components/banner-splash/banner-splash.test.ts +++ b/docs/components/banner-splash/banner-splash.test.ts @@ -25,6 +25,7 @@ describe('Components/Banner Splash', () => { afterEach(() => { bannerSplash.remove(); + // @ts-expect-error bannerSplash = undefined; }); }); diff --git a/docs/components/capability-box/capability-box.test.ts b/docs/components/capability-box/capability-box.test.ts index f335899b..485a9654 100644 --- a/docs/components/capability-box/capability-box.test.ts +++ b/docs/components/capability-box/capability-box.test.ts @@ -56,6 +56,7 @@ describe('Components/Capability Box', () => { afterEach(() => { capabilityBox.remove(); + // @ts-ignore capabilityBox = undefined; }); }); diff --git a/docs/components/ctc-block/ctc-block.ts b/docs/components/ctc-block/ctc-block.ts index b2bb344b..f6d21950 100644 --- a/docs/components/ctc-block/ctc-block.ts +++ b/docs/components/ctc-block/ctc-block.ts @@ -7,11 +7,13 @@ const template = document.createElement('template'); export default class CopyToClipboardBlock extends HTMLElement { selectCommandRunnerIdx; snippetContents; + root: ShadowRoot | null; constructor() { super(); this.selectCommandRunnerIdx = 0; this.snippetContents = ''; + this.root = null; } connectedCallback() { @@ -48,23 +50,26 @@ export default class CopyToClipboardBlock extends HTMLElement { console.warn(`Unknown variant provided => ${variant}`); } - this.attachShadow({ mode: 'open' }); - this.shadowRoot.appendChild(template.content.cloneNode(true)); - - switch (variant) { - case 'snippet': - this.shadowRoot - .querySelector('.copy-icon') - .addEventListener('click', this.copySnippetToClipboard.bind(this)); - break; - case 'shell': - this.shadowRoot - .querySelector('.copy-icon') - .addEventListener('click', this.copyShellScriptToClipboard.bind(this)); - break; - } + this.root = this.attachShadow({ mode: 'open' }); + + if (this.root) { + this.root.appendChild(template.content.cloneNode(true)); + + switch (variant) { + case 'snippet': + this.root + ?.querySelector('.copy-icon') + ?.addEventListener('click', this.copySnippetToClipboard.bind(this)); + break; + case 'shell': + this.root + ?.querySelector('.copy-icon') + ?.addEventListener('click', this.copyShellScriptToClipboard.bind(this)); + break; + } - this.shadowRoot.adoptedStyleSheets = [theme, sheet]; + this.root.adoptedStyleSheets = [theme, sheet]; + } } } @@ -76,7 +81,12 @@ export default class CopyToClipboardBlock extends HTMLElement { } copyShellScriptToClipboard() { - const contents = this.getAttribute('paste-contents').trim(); + const contents = this.getAttribute('paste-contents')?.trim(); + + if (!contents) { + console.warn('No content provided to copy to clipboard'); + return; + } navigator.clipboard.writeText(contents); console.log('copying the following contents to your clipboard =>', contents); diff --git a/docs/components/ctc-button/ctc-button.test.ts b/docs/components/ctc-button/ctc-button.test.ts index b2cd9aee..79ba7670 100644 --- a/docs/components/ctc-button/ctc-button.test.ts +++ b/docs/components/ctc-button/ctc-button.test.ts @@ -18,14 +18,15 @@ describe('Components/Copy To Clipboard (Button)', () => { }); it('should have an icon with the user provided content set', () => { - const icon = ctc.shadowRoot.querySelectorAll("[title='Copy to clipboard']"); + const icon = ctc?.shadowRoot?.querySelectorAll("[title='Copy to clipboard']"); - expect(icon.length).to.equal(1); + expect(icon?.length).to.equal(1); }); }); afterEach(() => { ctc.remove(); + // @ts-expect-error ctc = undefined; }); }); diff --git a/docs/components/ctc-button/ctc-button.tsx b/docs/components/ctc-button/ctc-button.tsx index 29d108cb..aba4295c 100644 --- a/docs/components/ctc-button/ctc-button.tsx +++ b/docs/components/ctc-button/ctc-button.tsx @@ -8,20 +8,35 @@ template.innerHTML = ` `; export default class CopyToClipboardButton extends HTMLElement { + root: ShadowRoot | null; + + constructor() { + super(); + this.root = null; + } + connectedCallback() { // bail of out of SSR entirely if (!this.shadowRoot && typeof window !== 'undefined') { - this.attachShadow({ mode: 'open' }); - this.shadowRoot.appendChild(template.content.cloneNode(true)); + this.root = this.attachShadow({ mode: 'open' }); + + if (this.root) { + this.root.appendChild(template.content.cloneNode(true)); + + this.root.adoptedStyleSheets = [sheet]; - this.shadowRoot.adoptedStyleSheets = [sheet]; + this.root.getElementById('icon')?.addEventListener('click', () => { + const contents = this.getAttribute('content'); - this.shadowRoot.getElementById('icon')?.addEventListener('click', () => { - const contents = this.getAttribute('content') ?? undefined; + if (!contents) { + console.warn('No content provided to copy to clipboard'); + return; + } - navigator.clipboard.writeText(contents); - console.log('copying the following contents to your clipboard =>', contents); - }); + navigator.clipboard.writeText(contents); + console.log('copying the following contents to your clipboard =>', contents); + }); + } } } } diff --git a/docs/components/feature-box/feature-box.test.ts b/docs/components/feature-box/feature-box.test.ts index afff6f72..3c97ebe2 100644 --- a/docs/components/feature-box/feature-box.test.ts +++ b/docs/components/feature-box/feature-box.test.ts @@ -68,6 +68,7 @@ describe('Components/Feature Box', () => { afterEach(() => { featureBox.remove(); + // @ts-expect-error featureBox = undefined; }); }); diff --git a/docs/components/footer/footer.test.ts b/docs/components/footer/footer.test.ts index 943af884..a0195353 100644 --- a/docs/components/footer/footer.test.ts +++ b/docs/components/footer/footer.test.ts @@ -38,6 +38,7 @@ describe('Components/Footer', () => { afterEach(() => { footer.remove(); + // @ts-expect-error footer = undefined; }); }); diff --git a/docs/components/header/header.test.ts b/docs/components/header/header.test.ts index 8acf8c93..3bd0edcf 100644 --- a/docs/components/header/header.test.ts +++ b/docs/components/header/header.test.ts @@ -35,7 +35,7 @@ describe('Components/Header', () => { }); it('should have an anchor tag with title attribute wrapping the logo', () => { - const anchor = header.querySelector("a[title='WCC Home Page']"); + const anchor = header.querySelector("a[title='WCC Home Page']") as HTMLAnchorElement; expect(anchor).to.not.equal(undefined); expect(anchor.getAttribute('href')).to.equal('/'); @@ -56,22 +56,23 @@ describe('Components/Header', () => { const navItem = pages.find((nav) => nav.route === link.getAttribute('href')); expect(navItem).to.not.equal(undefined); - expect(navItem.data.order).to.equal((idx += 1)); - expect(link.textContent).to.equal(navItem.label); + expect(navItem?.data.order).to.equal((idx += 1)); + expect(link.textContent).to.equal(navItem?.label); // Home page doesn't have a title, for example // maybe a Greenwood bug? - if (navItem.title) { - expect(link.getAttribute('title')).to.equal(navItem.title); + if (navItem?.title) { + expect(link.getAttribute('title')).to.equal(navItem?.title); } // current route should display as active - if (navItem.route === CURRENT_ROUTE && link.getAttribute('class').includes('active')) { - activeRoute = navItem; + if (navItem?.route === CURRENT_ROUTE && link?.getAttribute('class')?.includes('active')) { + activeRoute = navItem as unknown as Page; } }); - expect(activeRoute.route).to.equal(CURRENT_ROUTE); + // @ts-expect-error + expect(activeRoute?.route).to.equal(CURRENT_ROUTE); }); }); @@ -106,33 +107,35 @@ describe('Components/Header', () => { it('should have the expected navigation links', () => { const links = header.querySelectorAll("nav[aria-label='Mobile'] ul li a"); - let activeRoute: Page = undefined; + let activeRoute: Page; Array.from(links).forEach((link, idx) => { const navItem = pages.find((nav) => nav.route === link.getAttribute('href')); expect(navItem).to.not.equal(undefined); - expect(navItem.data.order).to.equal((idx += 1)); - expect(link.textContent).to.equal(navItem.label); + expect(navItem?.data.order).to.equal((idx += 1)); + expect(link.textContent).to.equal(navItem?.label); // Home page doesn't have a title, for example // maybe a Greenwood bug? - if (navItem.title) { + if (navItem?.title) { expect(link.getAttribute('title')).to.equal(navItem.title); } // current route should display as active - if (navItem.route === CURRENT_ROUTE && link.getAttribute('class').includes('active')) { - activeRoute = navItem; + if (navItem?.route === CURRENT_ROUTE && link?.getAttribute('class')?.includes('active')) { + activeRoute = navItem as unknown as Page; } }); - expect(activeRoute.route).to.equal(CURRENT_ROUTE); + // @ts-expect-error + expect(activeRoute?.route).to.equal(CURRENT_ROUTE); }); }); afterEach(() => { header.remove(); + // @ts-expect-error header = undefined; }); diff --git a/docs/components/sandbox/signal-counter.tsx b/docs/components/sandbox/signal-counter.tsx index 116821fe..33f9761e 100644 --- a/docs/components/sandbox/signal-counter.tsx +++ b/docs/components/sandbox/signal-counter.tsx @@ -25,7 +25,9 @@ export default class SignalCounter extends HTMLElement { this.render(); } - this.shadowRoot.adoptedStyleSheets = [sheet]; + if (this.shadowRoot) { + this.shadowRoot.adoptedStyleSheets = [sheet]; + } } increment() { diff --git a/docs/components/side-nav/side-nav.test.ts b/docs/components/side-nav/side-nav.test.ts index b27252e6..8d478008 100644 --- a/docs/components/side-nav/side-nav.test.ts +++ b/docs/components/side-nav/side-nav.test.ts @@ -89,7 +89,7 @@ describe('Components/Side Nav', () => { nav.setAttribute('route', ROUTE); nav.setAttribute('heading', HEADING); - expectedDocsContent = GRAPH.find((page) => page.route === ROUTE); + expectedDocsContent = GRAPH.find((page) => page.route === ROUTE) as DocsPage; document.body.appendChild(nav); @@ -101,7 +101,7 @@ describe('Components/Side Nav', () => { let fullMenu: HTMLElement; beforeEach(async () => { - fullMenu = nav.querySelector('#main-menu'); + fullMenu = nav.querySelector('#main-menu') as HTMLElement; }); it('should not be null', () => { @@ -118,18 +118,21 @@ describe('Components/Side Nav', () => { it('should have the expected number of section heading links', () => { const links = fullMenu.querySelectorAll('ul li a'); - expect(links.length).to.equal(expectedDocsContent.data.tableOfContents.length); + expect(links.length).to.equal(expectedDocsContent.data?.tableOfContents?.length); }); it('should have the expected content for section headings links', () => { const links = fullMenu.querySelectorAll('ul li a'); links.forEach((link, index) => { - expect(link.textContent).to.equal(expectedDocsContent.data.tableOfContents[index].content); + expect(link.textContent).to.equal( + expectedDocsContent.data?.tableOfContents?.[index]?.content, + ); }); }); afterAll(() => { + // @ts-expect-error fullMenu = null; }); }); @@ -139,7 +142,7 @@ describe('Components/Side Nav', () => { let popoverSelector = 'compact-menu'; beforeEach(async () => { - compactMenu = nav.querySelector(`#mobile-menu`); + compactMenu = nav.querySelector(`#mobile-menu`) as HTMLElement; }); it('should not be null', () => { @@ -179,24 +182,28 @@ describe('Components/Side Nav', () => { it('should have the expected number of section heading links', () => { const links = compactMenu.querySelectorAll('ul li a'); - expect(links.length).to.equal(expectedDocsContent.data.tableOfContents.length); + expect(links.length).to.equal(expectedDocsContent.data?.tableOfContents?.length); }); it('should have the expected content for section headings links', () => { const links = compactMenu.querySelectorAll('ul li a'); links.forEach((link, index) => { - expect(link.textContent).to.equal(expectedDocsContent.data.tableOfContents[index].content); + expect(link.textContent).to.equal( + expectedDocsContent.data?.tableOfContents?.[index]?.content, + ); }); }); afterEach(() => { + // @ts-expect-error compactMenu = null; }); }); afterEach(() => { nav.remove(); + // @ts-expect-error nav = undefined; }); diff --git a/docs/components/side-nav/side-nav.tsx b/docs/components/side-nav/side-nav.tsx index 109055ed..bbe240a6 100644 --- a/docs/components/side-nav/side-nav.tsx +++ b/docs/components/side-nav/side-nav.tsx @@ -14,14 +14,14 @@ export type DocsPage = Page & { }; export default class SideNav extends HTMLElement { - route: string; - toc: TableOfContents; - heading: string; + route: string = ''; + toc: TableOfContents = []; + heading: string = ''; async connectedCallback() { const route = this.getAttribute('route') ?? ''; const heading = this.getAttribute('heading') ?? ''; - const page: DocsPage = (await getContent()).find((page) => page.route === route); + const page: DocsPage = (await getContent()).find((page) => page.route === route) as DocsPage; this.heading = heading; this.toc = page?.data?.tableOfContents ?? []; diff --git a/docs/components/social-tray/social-tray.test.ts b/docs/components/social-tray/social-tray.test.ts index 589d60bd..9a6f92d8 100644 --- a/docs/components/social-tray/social-tray.test.ts +++ b/docs/components/social-tray/social-tray.test.ts @@ -48,7 +48,7 @@ describe('Components/Social Tray', () => { const iconItem = ICONS.find((icon) => icon.title === link.getAttribute('title')); expect(iconItem).to.not.equal(undefined); - expect(link.getAttribute('href')).to.equal(iconItem.link); + expect(link.getAttribute('href')).to.equal(iconItem?.link); expect(link.getAttribute('target')).equal('_blank'); }); }); @@ -56,6 +56,7 @@ describe('Components/Social Tray', () => { afterEach(() => { tray.remove(); + // @ts-expect-error tray = undefined; }); }); diff --git a/package-lock.json b/package-lock.json index 8717defe..344c321e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55,7 +55,7 @@ "remark-github": "^10.0.1", "stylelint": "^17.4.0", "stylelint-config-recommended": "^18.0.0", - "typescript": "^5.9.3", + "typescript": "^6.0.3", "typescript-eslint": "^8.46.2", "vite": "^7.3.1", "vitest": "^4.0.18" @@ -13748,9 +13748,9 @@ } }, "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, "license": "Apache-2.0", "bin": { diff --git a/package.json b/package.json index f76d02b1..4a0a440a 100644 --- a/package.json +++ b/package.json @@ -103,7 +103,7 @@ "remark-github": "^10.0.1", "stylelint": "^17.4.0", "stylelint-config-recommended": "^18.0.0", - "typescript": "^5.9.3", + "typescript": "^6.0.3", "typescript-eslint": "^8.46.2", "vite": "^7.3.1", "vitest": "^4.0.18" diff --git a/src/jsx.d.ts b/src/jsx.d.ts index 4c9286bb..ca6e96a5 100644 --- a/src/jsx.d.ts +++ b/src/jsx.d.ts @@ -9,13 +9,15 @@ type ElementAttributes = { class?: string; }; +type PopoverState = 'auto' | 'manual' | 'hint'; type PopoverTargetAction = 'show' | 'hide' | 'toggle'; -type PopoverTargetAttributes = { +type PopoverAttributes = { // have to manage this manually, can't seem to get this from TypeScript itself (not sure if just skill issue? :D) // https://github.com/microsoft/TypeScript-DOM-lib-generator/issues/1790 // it should be there per https://github.com/mdn/browser-compat-data/pull/21875 // https://github.com/ProjectEvergreen/wcc/issues/236 // per the spec, this should only apply to - + {/* TODO: https://github.com/ProjectEvergreen/wcc/issues/88 */} + {/* @ts-expect-error - onclick should be.a function, but we coerce to an assignment */} + You have clicked{' '} @@ -44,6 +44,8 @@ export default class Counter extends HTMLElement { {' '} times + {/* TODO: https://github.com/ProjectEvergreen/wcc/issues/88 */} + {/* @ts-expect-error - onclick should be.a function, but we coerce to an assignment */} diff --git a/test/cases/tsx/src/header.tsx b/test/cases/tsx/src/header.tsx index 4149a5a4..e69e21bb 100644 --- a/test/cases/tsx/src/header.tsx +++ b/test/cases/tsx/src/header.tsx @@ -7,6 +7,11 @@ export default class Header extends HTMLElement { Close + {/* @ts-expect-error popovertargetaction should only be valid if its one of the supported values */} + + {/* @ts-expect-error popovertarget should only be valid on