diff --git a/plugins/techdocs/src/reader/components/Reader.tsx b/plugins/techdocs/src/reader/components/Reader.tsx
index 42be2732d0..1975d350e1 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,16 +134,25 @@ export const Reader = () => {
shadowRoot?.querySelector(parsedUrl.hash)?.scrollIntoView();
},
}),
+ onCssReady({
+ docStorageURL,
+ onLoading: (dom: Element) => {
+ (dom as HTMLElement).style.setProperty('opacity', '0');
+ },
+ onLoaded: (dom: Element) => {
+ (dom as HTMLElement).style.removeProperty('opacity');
+ },
+ }),
]);
}, [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/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.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));
+ });
+});
diff --git a/plugins/techdocs/src/reader/transformers/onCssReady.ts b/plugins/techdocs/src/reader/transformers/onCssReady.ts
new file mode 100644
index 0000000000..2d355574b6
--- /dev/null
+++ b/plugins/techdocs/src/reader/transformers/onCssReady.ts
@@ -0,0 +1,53 @@
+/*
+ * 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;
+ onLoading: (dom: Element) => void;
+ onLoaded: (dom: Element) => void;
+};
+
+export const onCssReady = ({
+ docStorageURL,
+ onLoading,
+ onLoaded,
+}: 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;
+
+ if (count > 0) {
+ onLoading(dom);
+ }
+
+ cssPages.forEach(cssPage =>
+ cssPage.addEventListener('load', () => {
+ count -= 1;
+
+ if (count === 0) {
+ onLoaded(dom);
+ }
+ }),
+ );
+
+ return dom;
+ };
+};
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;
+};