From 0c1e1bf346be5f4442bd009e989ed7c66c929fed Mon Sep 17 00:00:00 2001 From: Bilawal Hameed Date: Fri, 3 Jul 2020 16:59:56 +0200 Subject: [PATCH 1/7] feat(techdocs): added onCssReady transformer --- .../techdocs/src/reader/components/Reader.tsx | 10 ++++ .../techdocs/src/reader/transformers/index.ts | 1 + .../src/reader/transformers/onCssReady.ts | 49 +++++++++++++++++++ 3 files changed, 60 insertions(+) create mode 100644 plugins/techdocs/src/reader/transformers/onCssReady.ts diff --git a/plugins/techdocs/src/reader/components/Reader.tsx b/plugins/techdocs/src/reader/components/Reader.tsx index 42be2732d0..3e840aacbc 100644 --- a/plugins/techdocs/src/reader/components/Reader.tsx +++ b/plugins/techdocs/src/reader/components/Reader.tsx @@ -27,6 +27,7 @@ import transformer, { addLinkClickListener, removeMkdocsHeader, modifyCss, + onCssReady, } from '../transformers'; import { docStorageURL } from '../../config'; import URLFormatter from '../urlFormatter'; @@ -133,6 +134,15 @@ export const Reader = () => { shadowRoot?.querySelector(parsedUrl.hash)?.scrollIntoView(); }, }), + onCssReady({ + docStorageURL, + onLoad: dom => { + dom.style.setProperty('opacity', '0'); + }, + onReady: dom => { + dom.style.removeProperty('opacity'); + }, + }), ]); }, [componentId, path, shadowRoot, state]); // eslint-disable-line react-hooks/exhaustive-deps diff --git a/plugins/techdocs/src/reader/transformers/index.ts b/plugins/techdocs/src/reader/transformers/index.ts index f35d6aa478..4e12dcae48 100644 --- a/plugins/techdocs/src/reader/transformers/index.ts +++ b/plugins/techdocs/src/reader/transformers/index.ts @@ -19,6 +19,7 @@ export * from './rewriteDocLinks'; export * from './addLinkClickListener'; export * from './removeMkdocsHeader'; export * from './modifyCss'; +export * from './onCssReady'; export type Transformer = (dom: Element) => Element; diff --git a/plugins/techdocs/src/reader/transformers/onCssReady.ts b/plugins/techdocs/src/reader/transformers/onCssReady.ts new file mode 100644 index 0000000000..af3db29b7d --- /dev/null +++ b/plugins/techdocs/src/reader/transformers/onCssReady.ts @@ -0,0 +1,49 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Transformer } from './index'; + +type OnCssReadyOptions = { + docStorageURL: string; + onCssLoading: (dom: Element) => void; + onCssReady: (dom: Element) => void; +}; + +export const onCssReady = ({ + docStorageURL, + onLoad, + onReady, +}: OnCssReadyOptions): Transformer => { + return dom => { + const cssPages = Array.from( + dom.querySelectorAll('head > link[rel="stylesheet"]'), + ).filter(elem => elem.getAttribute('href').startsWith(docStorageURL)); + + let count = cssPages.length; + + onLoad(dom); + + cssPages.forEach(cssPage => + cssPage.addEventListener('load', () => { + count -= 1; + + if (count === 0) { + onReady(dom); + } + }), + ); + }; +}; From b44d4a4467f81a9336061f1b6f80b6951a94fc3c Mon Sep 17 00:00:00 2001 From: Bilawal Hameed Date: Mon, 6 Jul 2020 14:01:44 +0200 Subject: [PATCH 2/7] fix: only run onLoading if external stylesheets exist --- plugins/techdocs/src/reader/transformers/onCssReady.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/techdocs/src/reader/transformers/onCssReady.ts b/plugins/techdocs/src/reader/transformers/onCssReady.ts index af3db29b7d..970f7cbe7c 100644 --- a/plugins/techdocs/src/reader/transformers/onCssReady.ts +++ b/plugins/techdocs/src/reader/transformers/onCssReady.ts @@ -34,7 +34,9 @@ export const onCssReady = ({ let count = cssPages.length; - onLoad(dom); + if (count > 0) { + onLoading(dom); + } cssPages.forEach(cssPage => cssPage.addEventListener('load', () => { From 4eca08efd1f6fed110eddc082bf4342825a31da0 Mon Sep 17 00:00:00 2001 From: Bilawal Hameed Date: Mon, 6 Jul 2020 14:02:16 +0200 Subject: [PATCH 3/7] chore: rename callbacks to onLoading and onLoaded --- plugins/techdocs/src/reader/components/Reader.tsx | 12 ++++++------ .../techdocs/src/reader/transformers/onCssReady.ts | 14 ++++++++------ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/plugins/techdocs/src/reader/components/Reader.tsx b/plugins/techdocs/src/reader/components/Reader.tsx index 3e840aacbc..e474d3bf11 100644 --- a/plugins/techdocs/src/reader/components/Reader.tsx +++ b/plugins/techdocs/src/reader/components/Reader.tsx @@ -146,13 +146,13 @@ export const Reader = () => { ]); }, [componentId, path, shadowRoot, state]); // eslint-disable-line react-hooks/exhaustive-deps - if (state.value instanceof Error) return ; + if (state.value instanceof Error) { + return ; + } return ( - <> - -
- - + +
+ ); }; diff --git a/plugins/techdocs/src/reader/transformers/onCssReady.ts b/plugins/techdocs/src/reader/transformers/onCssReady.ts index 970f7cbe7c..2d355574b6 100644 --- a/plugins/techdocs/src/reader/transformers/onCssReady.ts +++ b/plugins/techdocs/src/reader/transformers/onCssReady.ts @@ -18,19 +18,19 @@ import type { Transformer } from './index'; type OnCssReadyOptions = { docStorageURL: string; - onCssLoading: (dom: Element) => void; - onCssReady: (dom: Element) => void; + onLoading: (dom: Element) => void; + onLoaded: (dom: Element) => void; }; export const onCssReady = ({ docStorageURL, - onLoad, - onReady, + onLoading, + onLoaded, }: OnCssReadyOptions): Transformer => { return dom => { const cssPages = Array.from( dom.querySelectorAll('head > link[rel="stylesheet"]'), - ).filter(elem => elem.getAttribute('href').startsWith(docStorageURL)); + ).filter(elem => elem.getAttribute('href')?.startsWith(docStorageURL)); let count = cssPages.length; @@ -43,9 +43,11 @@ export const onCssReady = ({ count -= 1; if (count === 0) { - onReady(dom); + onLoaded(dom); } }), ); + + return dom; }; }; From 04067f24f02697563209ecd294cd06dd183c533b Mon Sep 17 00:00:00 2001 From: Bilawal Hameed Date: Mon, 6 Jul 2020 14:02:45 +0200 Subject: [PATCH 4/7] chore: add onCssReady tests --- .../reader/transformers/onCssReady.test.ts | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 plugins/techdocs/src/reader/transformers/onCssReady.test.ts diff --git a/plugins/techdocs/src/reader/transformers/onCssReady.test.ts b/plugins/techdocs/src/reader/transformers/onCssReady.test.ts new file mode 100644 index 0000000000..10edbbae5e --- /dev/null +++ b/plugins/techdocs/src/reader/transformers/onCssReady.test.ts @@ -0,0 +1,87 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + FIXTURES, + createTestShadowDom, + mockStylesheetEventListener, + executeStylesheetEventListeners, + clearStylesheetEventListeners, +} from '../../test-utils'; +import { addBaseUrl, onCssReady } from '../transformers'; + +const docStorageURL: string = + 'https://techdocs-mock-sites.storage.googleapis.com'; + +jest.useFakeTimers(); + +describe('onCssReady', () => { + beforeEach(() => { + mockStylesheetEventListener(100); + }); + + afterEach(() => { + clearStylesheetEventListeners(); + }); + + it('does not call onLoading and onLoaded without the addBaseUrl transformer', () => { + const onLoading = jest.fn(); + const onLoaded = jest.fn(); + + createTestShadowDom(FIXTURES.FIXTURE_STANDARD_PAGE, { + transformers: [ + onCssReady({ + docStorageURL, + onLoading, + onLoaded, + }), + ], + }); + + expect(onLoading).not.toHaveBeenCalled(); + executeStylesheetEventListeners(); + expect(onLoaded).not.toHaveBeenCalled(); + }); + + it('calls the onLoading and onLoaded correctly', () => { + const onLoading = jest.fn(); + const onLoaded = jest.fn(); + + createTestShadowDom(FIXTURES.FIXTURE_STANDARD_PAGE, { + transformers: [ + addBaseUrl({ + docStorageURL, + componentId: 'mkdocs', + path: '', + }), + onCssReady({ + docStorageURL, + onLoading, + onLoaded, + }), + ], + }); + + expect(onLoading).toHaveBeenCalledTimes(1); + expect(onLoading).toHaveBeenCalledWith(expect.any(Element)); + expect(onLoaded).not.toHaveBeenCalled(); + + executeStylesheetEventListeners(); + + expect(onLoaded).toHaveBeenCalledTimes(1); + expect(onLoaded).toHaveBeenCalledWith(expect.any(Element)); + }); +}); From 1040383d36f3d24b64ff2ee7f286cc72ab4afd03 Mon Sep 17 00:00:00 2001 From: Bilawal Hameed Date: Mon, 6 Jul 2020 14:03:04 +0200 Subject: [PATCH 5/7] chore: added stylesheet test-util + split out shadow-dom test util --- plugins/techdocs/src/test-utils/index.ts | 43 +------------- plugins/techdocs/src/test-utils/shadowDom.ts | 58 +++++++++++++++++++ .../techdocs/src/test-utils/stylesheets.ts | 35 +++++++++++ 3 files changed, 95 insertions(+), 41 deletions(-) create mode 100644 plugins/techdocs/src/test-utils/shadowDom.ts create mode 100644 plugins/techdocs/src/test-utils/stylesheets.ts diff --git a/plugins/techdocs/src/test-utils/index.ts b/plugins/techdocs/src/test-utils/index.ts index f9ced5f930..d782f225d5 100644 --- a/plugins/techdocs/src/test-utils/index.ts +++ b/plugins/techdocs/src/test-utils/index.ts @@ -15,49 +15,10 @@ */ import FIXTURE_STANDARD_PAGE from './fixtures/mkdocs-index'; -import transformer from '../reader/transformers'; -import type { Transformer } from '../reader/transformers'; export const FIXTURES = { FIXTURE_STANDARD_PAGE, }; -export type CreateTestShadowDomOptions = { - transformers: Transformer[]; -}; - -export const createTestShadowDom = ( - fixture: string, - opts: CreateTestShadowDomOptions = { transformers: [] }, -): ShadowRoot => { - const divElement = document.createElement('div'); - divElement.attachShadow({ mode: 'open' }); - document.body.appendChild(divElement); - - const domParser = new DOMParser().parseFromString(fixture, 'text/html'); - divElement.shadowRoot?.appendChild(domParser.documentElement); - - if (opts.transformers) { - transformer(divElement.shadowRoot!.children[0], opts.transformers); - } - - return divElement.shadowRoot!; -}; - -export const getSample = ( - shadowDom: ShadowRoot, - elementName: string, - elementAttribute: string, - sampleSize = 2, -) => { - const rootElement = shadowDom.children[0]; - - return Array.from(rootElement.getElementsByTagName(elementName)) - .filter(elem => { - return elem.hasAttribute(elementAttribute); - }) - .slice(0, sampleSize) - .map(elem => { - return elem.getAttribute(elementAttribute); - }); -}; +export * from './stylesheets'; +export * from './shadowDom'; diff --git a/plugins/techdocs/src/test-utils/shadowDom.ts b/plugins/techdocs/src/test-utils/shadowDom.ts new file mode 100644 index 0000000000..ded6b6a860 --- /dev/null +++ b/plugins/techdocs/src/test-utils/shadowDom.ts @@ -0,0 +1,58 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import transformer from '../reader/transformers'; +import type { Transformer } from '../reader/transformers'; + +export type CreateTestShadowDomOptions = { + transformers: Transformer[]; +}; + +export const createTestShadowDom = ( + fixture: string, + opts: CreateTestShadowDomOptions = { transformers: [] }, +): ShadowRoot => { + const divElement = document.createElement('div'); + divElement.attachShadow({ mode: 'open' }); + document.body.appendChild(divElement); + + const domParser = new DOMParser().parseFromString(fixture, 'text/html'); + divElement.shadowRoot?.appendChild(domParser.documentElement); + + if (opts.transformers) { + transformer(divElement.shadowRoot!.children[0], opts.transformers); + } + + return divElement.shadowRoot!; +}; + +export const getSample = ( + shadowDom: ShadowRoot, + elementName: string, + elementAttribute: string, + sampleSize = 2, +) => { + const rootElement = shadowDom.children[0]; + + return Array.from(rootElement.getElementsByTagName(elementName)) + .filter(elem => { + return elem.hasAttribute(elementAttribute); + }) + .slice(0, sampleSize) + .map(elem => { + return elem.getAttribute(elementAttribute); + }); +}; diff --git a/plugins/techdocs/src/test-utils/stylesheets.ts b/plugins/techdocs/src/test-utils/stylesheets.ts new file mode 100644 index 0000000000..0532c71272 --- /dev/null +++ b/plugins/techdocs/src/test-utils/stylesheets.ts @@ -0,0 +1,35 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const mockStylesheetEventListener = (timeToCallbackMs: number): void => { + HTMLLinkElement.prototype.addEventListener = ( + _eventName: string, + eventCallback: any, + ) => { + setTimeout(() => { + eventCallback(); + }, timeToCallbackMs); + }; +}; + +export const executeStylesheetEventListeners = (): void => { + jest.runOnlyPendingTimers(); +}; + +export const clearStylesheetEventListeners = (): void => { + HTMLLinkElement.prototype.addEventListener = + Element.prototype.addEventListener; +}; From 180a08a5ecad6cf3685e8b0c780733881bd7cbf8 Mon Sep 17 00:00:00 2001 From: Bilawal Hameed Date: Mon, 6 Jul 2020 14:24:23 +0200 Subject: [PATCH 6/7] chore: use onLoading and onLoaded --- plugins/techdocs/src/reader/components/Reader.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/techdocs/src/reader/components/Reader.tsx b/plugins/techdocs/src/reader/components/Reader.tsx index e474d3bf11..e7955161f7 100644 --- a/plugins/techdocs/src/reader/components/Reader.tsx +++ b/plugins/techdocs/src/reader/components/Reader.tsx @@ -136,10 +136,10 @@ export const Reader = () => { }), onCssReady({ docStorageURL, - onLoad: dom => { + onLoading: (dom: Element) => { dom.style.setProperty('opacity', '0'); }, - onReady: dom => { + onLoaded: (dom: Element) => { dom.style.removeProperty('opacity'); }, }), From 9a3b6d50fe4d627bb08acea08883fa64c957b7b2 Mon Sep 17 00:00:00 2001 From: Bilawal Hameed Date: Mon, 6 Jul 2020 14:29:28 +0200 Subject: [PATCH 7/7] fix: cast as HTMLElement --- plugins/techdocs/src/reader/components/Reader.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/techdocs/src/reader/components/Reader.tsx b/plugins/techdocs/src/reader/components/Reader.tsx index e7955161f7..1975d350e1 100644 --- a/plugins/techdocs/src/reader/components/Reader.tsx +++ b/plugins/techdocs/src/reader/components/Reader.tsx @@ -137,10 +137,10 @@ export const Reader = () => { onCssReady({ docStorageURL, onLoading: (dom: Element) => { - dom.style.setProperty('opacity', '0'); + (dom as HTMLElement).style.setProperty('opacity', '0'); }, onLoaded: (dom: Element) => { - dom.style.removeProperty('opacity'); + (dom as HTMLElement).style.removeProperty('opacity'); }, }), ]);