Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(entry/files): prevent mismatch with pathname #268

Merged
merged 5 commits into from
Dec 28, 2021
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
8 changes: 8 additions & 0 deletions .codesandbox/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@
"format": {
"name": "Format Workspace",
"command": "yarn format"
},
"watch:tests": {
"name": "Watch tests",
"command": "yarn test --watch"
},
"lint": {
"name": "Lint Workspace",
"command": "yarn lint"
}
},
"setupTasks": [
Expand Down
31 changes: 31 additions & 0 deletions sandpack-react/src/components/FileTabs/FileTabs.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from "react";
import { create } from "react-test-renderer";

import { SandpackProvider } from "../../contexts/sandpackContext";
import { SandpackCodeEditor } from "../CodeEditor";

describe("FileTabs", () => {
it("doesn't have duplicate filename", () => {
const component = create(
<SandpackProvider
customSetup={{
files: {
"/foo/App.js": "",
"/App.js": "",
"/baz/App.js": "",
},
}}
template="react"
>
<SandpackCodeEditor />
</SandpackProvider>
).root;

const buttons = component.findAllByProps({
className: "sp-tab-button",
});
const buttonsTex = buttons.map((item) => item.props.children[0]);

expect(buttonsTex).toEqual(["foo/App.js", "App.js", "baz/App.js"]);
});
});
35 changes: 33 additions & 2 deletions sandpack-react/src/components/FileTabs/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import * as React from "react";

import { useSandpack } from "../../hooks/useSandpack";
import { CloseIcon } from "../../icons";
import { getFileName } from "../../utils/stringUtils";
import {
calculateNearestUniquePath,
getFileName,
} from "../../utils/stringUtils";

export interface FileTabsProps {
closableTabs?: boolean;
Expand All @@ -30,6 +33,34 @@ export const FileTabs = ({ closableTabs }: FileTabsProps): JSX.Element => {
sandpack.closeFile(pathToClose);
};

const getTriggerText = (currentPath: string): string => {
const documentFileName = getFileName(currentPath);

const pathsWithDuplicateFileNames = openPaths.reduce((prev, curr) => {
if (curr === currentPath) {
return prev;
}

const fileName = getFileName(curr);

if (fileName === documentFileName) {
prev.push(curr);
return prev;
}

return prev;
}, [] as string[]);

if (pathsWithDuplicateFileNames.length === 0) {
return documentFileName;
} else {
return calculateNearestUniquePath(
currentPath,
pathsWithDuplicateFileNames
);
}
};

return (
<div className={c("tabs")} translate="no">
<div
Expand All @@ -48,7 +79,7 @@ export const FileTabs = ({ closableTabs }: FileTabsProps): JSX.Element => {
title={filePath}
type="button"
>
{getFileName(filePath)}
{getTriggerText(filePath)}
{closableTabs && openPaths.length > 1 ? (
<span className={c("close-button")} onClick={handleCloseFile}>
<CloseIcon />
Expand Down
4 changes: 0 additions & 4 deletions sandpack-react/src/components/FileTabs/utils.tsx

This file was deleted.

12 changes: 6 additions & 6 deletions sandpack-react/src/templates/react-typescript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export const REACT_TYPESCRIPT_TEMPLATE: SandboxTemplate = {
"tsconfig.json": {
code: `{
"include": [
"./src/**/*"
"./**/*"
],
"compilerOptions": {
"strict": true,
Expand All @@ -18,13 +18,13 @@ export const REACT_TYPESCRIPT_TEMPLATE: SandboxTemplate = {
}
}`,
},
"/src/App.tsx": {
"/App.tsx": {
code: `export default function App(): JSX.Element {
return <h1>Hello World</h1>
}
`,
},
"/src/index.tsx": {
"/index.tsx": {
code: `import React, { StrictMode } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
Expand All @@ -39,7 +39,7 @@ ReactDOM.render(
rootElement
);`,
},
"/src/styles.css": {
"/styles.css": {
code: `body {
font-family: sans-serif;
-webkit-font-smoothing: auto;
Expand Down Expand Up @@ -80,7 +80,7 @@ h1 {
"@types/react-dom": "^17.0.0",
typescript: "^4.0.0",
},
entry: "/src/index.tsx",
main: "/src/App.tsx",
entry: "/index.tsx",
main: "/App.tsx",
environment: "create-react-app",
};
5 changes: 5 additions & 0 deletions sandpack-react/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ export interface SandpackSetup {
* ```
*/
dependencies?: Record<string, string>;
/**
* The entry file is the starting point of the bundle process.
*
* If you change the path of the entry file, make sure you control all the files that go into the bundle process, as prexisting settings in the template might not work anymore.
*/
entry?: string;
main?: string;
files?: SandpackFiles;
Expand Down
46 changes: 46 additions & 0 deletions sandpack-react/src/utils/stringUtils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { calculateNearestUniquePath } from "./stringUtils";

describe(calculateNearestUniquePath, () => {
it("strips the leading slash from the root path", () => {
expect(
calculateNearestUniquePath("/index.js", [
"/other/index.js",
"/test/index.js",
])
).toBe("index.js");
});

it("supports nested paths", () => {
expect(
calculateNearestUniquePath("/test/index.js", [
"/index.js",
"/other/index.js",
"/other/something/index.js",
])
).toBe("test/index.js");

expect(
calculateNearestUniquePath("/test/something/index.js", [
"/index.js",
"/other/index.js",
"/other/something/index.js",
])
).toBe("test/something/index.js");
});

it("adds a leading `..` when other open paths have the same fileName, but different paths", () => {
expect(
calculateNearestUniquePath("/test/something/index.js", [
"/index.js",
"/example/index.js",
"/example/something/else/index.js",
])
).toBe("../something/index.js");
});

it("supports root paths", () => {
expect(
calculateNearestUniquePath("README.md", [".gitignore", ".eslintignore"])
).toBe("README.md");
});
});
46 changes: 46 additions & 0 deletions sandpack-react/src/utils/stringUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,52 @@ export const getFileName = (filePath: string): string => {
return filePath.slice(lastIndexOfSlash + 1);
};

export const calculateNearestUniquePath = (
currentPath: string,
otherPaths: string[]
): string => {
const currentPathParts = currentPath.split("/");
const resultPathParts: string[] = [];

// If path is on root, there are no parts to loop through
if (currentPathParts.length === 1) {
resultPathParts.unshift(currentPathParts[0]);
} else {
// Loop over all other paths to find a unique path
for (let fileIndex = 0; fileIndex < otherPaths.length; fileIndex++) {
// We go over each part of the path from end to start to find the closest unique directory
const otherPathParts = otherPaths[fileIndex].split("/");
for (
let partsFromEnd = 1;
partsFromEnd < currentPathParts.length;
partsFromEnd++
) {
const currentPathPart =
currentPathParts[currentPathParts.length - partsFromEnd];
const otherPathPart =
otherPathParts[otherPathParts.length - partsFromEnd];
// If this part hasn't been added to the result path, we add it here
if (resultPathParts.length < partsFromEnd) {
resultPathParts.unshift(currentPathPart);
}
// If this part is different between the current path and other path we break
// as from this moment the current path is unique compared to this other path
if (currentPathPart !== otherPathPart) {
break;
}
}
}
}

// Add `..` if this is a relative path
if (resultPathParts.length + 1 < currentPathParts.length) {
resultPathParts.unshift("..");
}

// Join the result path parts into a path string
return resultPathParts.join("/");
};

export const hexToRGB = (
hex: string
): { red: number; green: number; blue: number } => {
Expand Down