Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/containers/WebComponentLoader.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ const WebComponentLoader = (props) => {
remixLoadFailed,
initialProject,
locale,
embedded,
});

useProjectPersistence({
Expand Down
24 changes: 24 additions & 0 deletions src/containers/WebComponentLoader.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -262,9 +262,28 @@ describe("When no user is in state", () => {
loadCache: true,
remixLoadFailed: false,
reactAppApiEndpoint: "http://localhost:3009",
embedded: false,
});
});

Comment thread
abcampo-iry marked this conversation as resolved.
test("Passes embedded prop to useProject hook", () => {
render(
<Provider store={store}>
<CookiesProvider cookies={cookies}>
<WebComponentLoader
code={code}
identifier={identifier}
embedded={true}
/>
</CookiesProvider>
</Provider>,
);

expect(useProject).toHaveBeenLastCalledWith(
expect.objectContaining({ embedded: true }),
);
});

test("Calls useProjectPersistence hook with correct attributes", () => {
expect(useProjectPersistence).toHaveBeenCalledWith({
project: {
Expand Down Expand Up @@ -409,6 +428,7 @@ describe("When no user is in state", () => {
loadCache: true,
remixLoadFailed: false,
reactAppApiEndpoint: "http://localhost:3009",
embedded: false,
});
});
});
Expand Down Expand Up @@ -536,6 +556,7 @@ describe("When user is in state", () => {
loadCache: true,
remixLoadFailed: false,
reactAppApiEndpoint: "http://localhost:3009",
embedded: false,
});
});

Expand Down Expand Up @@ -568,6 +589,7 @@ describe("When user is in state", () => {
loadCache: true,
remixLoadFailed: false,
reactAppApiEndpoint: "http://localhost:3009",
embedded: false,
});
});
});
Expand Down Expand Up @@ -650,6 +672,7 @@ describe("When user is in state", () => {
loadCache: true,
remixLoadFailed: true,
reactAppApiEndpoint: "http://localhost:3009",
embedded: false,
});
});

Expand Down Expand Up @@ -813,6 +836,7 @@ describe("when a Scratch remix updates the project identifier", () => {
loadCache: true,
remixLoadFailed: false,
reactAppApiEndpoint: "http://localhost:3009",
embedded: false,
});
});
});
Expand Down
26 changes: 17 additions & 9 deletions src/hooks/useProject.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,23 @@ export const useProject = ({
loadCache = true,
remixLoadFailed = false,
locale = null,
embedded = false,
}) => {
const loading = useSelector((state) => state.editor.loading);
const isEmbedded = useSelector((state) => state.editor.isEmbedded);
const isBrowserPreview = useSelector((state) => state.editor.browserPreview);
const browserPreviewFromQuery =
new URLSearchParams(window.location.search).get("browserPreview") ===
"true";
const isEmbeddedMode = embedded || isEmbedded;
const canUseBrowserPreviewCache =
isBrowserPreview || (embedded && browserPreviewFromQuery);
const shouldSkipCache = isEmbeddedMode && !canUseBrowserPreviewCache;
const project = useSelector((state) => state.editor.project);
const loadDispatched = useRef(false);

const getCachedProject = (id) =>
isEmbedded && !isBrowserPreview
? null
: JSON.parse(localStorage.getItem(id || "project"));
shouldSkipCache ? null : JSON.parse(localStorage.getItem(id || "project"));
const [cachedProject, setCachedProject] = useState(
getCachedProject(projectIdentifier),
);
Expand All @@ -51,21 +57,23 @@ export const useProject = ({
return;
}

const is_cached_saved_project =
const isCachedSavedProject =
projectIdentifier &&
cachedProject &&
cachedProject.identifier === projectIdentifier;
const is_cached_unsaved_project =
const isCachedUnsavedProject =
!projectIdentifier && cachedProject && !initialProject;
const cachedProjectMatchesRequest =
isCachedSavedProject || isCachedUnsavedProject;

// At the moment this will never match because the cachedProject doesn't have a locale attribute (yet),
// so this will always be false, which effectively disables the whole caching mechanism
// Browser previews need the current local edits. Starter projects can be
// served from a fallback locale, so the cached locale may not match the URL.
const cachedLocaleMatches = cachedProject?.locale === effectiveLocale;

if (
loadCache &&
(is_cached_saved_project || is_cached_unsaved_project) &&
cachedLocaleMatches
cachedProjectMatchesRequest &&
(cachedLocaleMatches || canUseBrowserPreviewCache)
) {
loadCachedProject();
return;
Expand Down
87 changes: 83 additions & 4 deletions src/hooks/useProject.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ describe("When not embedded", () => {
expect(setProject).toHaveBeenCalledWith(initialProject);
});

test("If cached project matches identifer and locale, uses cached project", () => {
test("If cached project matches identifier and locale, uses cached project", () => {
localStorage.setItem(
cachedProject.identifier,
JSON.stringify(cachedProject),
Expand All @@ -92,7 +92,7 @@ describe("When not embedded", () => {
expect(setProject).toHaveBeenCalledWith(cachedProject);
});

test("If cached project matches identifer and locale, clears cached project", () => {
test("If cached project matches identifier and locale, keeps cached project", () => {
localStorage.setItem(
cachedProject.identifier,
JSON.stringify(cachedProject),
Expand All @@ -105,10 +105,33 @@ describe("When not embedded", () => {
}),
{ wrapper },
);
expect(localStorage.getItem("project")).toBeNull();
expect(JSON.parse(localStorage.getItem(cachedProject.identifier))).toEqual(
cachedProject,
);
});

test("If cached project does not match identifer, does not use cached project", async () => {
test("If embedded prop is true before embedded state is set, loads from server instead of cache", () => {
syncProject.mockImplementation(jest.fn((_) => jest.fn()));
localStorage.setItem(
cachedProject.identifier,
JSON.stringify(cachedProject),
);
renderHook(
() =>
useProject({
projectIdentifier: cachedProject.identifier,
locale: cachedProject.locale,
embedded: true,
accessToken,
reactAppApiEndpoint,
}),
{ wrapper },
);
expect(syncProject).toHaveBeenCalledWith("load");
expect(setProject).not.toHaveBeenCalledWith(cachedProject);
});

test("If cached project does not match identifier, does not use cached project", async () => {
syncProject.mockImplementationOnce(jest.fn((_) => jest.fn()));
localStorage.setItem("project", JSON.stringify(cachedProject));
renderHook(
Expand Down Expand Up @@ -140,6 +163,31 @@ describe("When not embedded", () => {
);
});

test("If cached project does not match locale and browserPreview query is used outside embedded viewer, does not use cached project", () => {
syncProject.mockImplementation(jest.fn((_) => jest.fn()));
window.history.pushState(
{},
"",
"/en-US/projects/hello-world-project?browserPreview=true&page=index.html",
);
localStorage.setItem(
cachedProject.identifier,
JSON.stringify(cachedProject),
);
renderHook(
() =>
useProject({
projectIdentifier: cachedProject.identifier,
locale: "en-US",
accessToken,
reactAppApiEndpoint,
}),
{ wrapper },
);
expect(syncProject).toHaveBeenCalledWith("load");
expect(setProject).not.toHaveBeenCalledWith(cachedProject);
});

test("If cached project does not match identifier and locale, loads correct uncached project", async () => {
syncProject.mockImplementationOnce(jest.fn((_) => loadProject));
localStorage.setItem("project", JSON.stringify(cachedProject));
Expand Down Expand Up @@ -559,6 +607,7 @@ describe("When not embedded", () => {

afterEach(() => {
localStorage.clear();
window.history.pushState({}, "", "/");
});
});

Expand All @@ -575,6 +624,35 @@ describe("When embedded", () => {
wrapper = ({ children }) => <Provider store={store}>{children}</Provider>;
});

test("If embedded browser preview and cached project locale does not match, uses cached project", () => {
const browserPreviewCachedProject = {
...cachedProject,
identifier: "blank-html-starter",
locale: "en",
};
Comment thread
abcampo-iry marked this conversation as resolved.
window.history.pushState(
{},
"",
"/en-US/embed/viewer/blank-html-starter?browserPreview=true&page=index.html",
);
localStorage.setItem(
browserPreviewCachedProject.identifier,
JSON.stringify(browserPreviewCachedProject),
);
renderHook(
() =>
useProject({
projectIdentifier: browserPreviewCachedProject.identifier,
locale: "en-US",
embedded: true,
accessToken,
reactAppApiEndpoint,
}),
{ wrapper },
);
expect(setProject).toHaveBeenCalledWith(browserPreviewCachedProject);
});

test("If embedded and cached project, loads from server", async () => {
syncProject.mockImplementationOnce(jest.fn((_) => loadProject));
localStorage.setItem("hello-world-project", JSON.stringify(cachedProject));
Expand All @@ -601,5 +679,6 @@ describe("When embedded", () => {

afterEach(() => {
localStorage.clear();
window.history.pushState({}, "", "/");
});
});
Loading