Skip to content

Commit

Permalink
Merge pull request #9578 from marmelab/create-react-admin-tests
Browse files Browse the repository at this point in the history
Add tests to the app generated by create-react-admin with ra-data-fakerest
  • Loading branch information
slax57 committed Jan 23, 2024
2 parents 0624af3 + f94356b commit a2f273f
Show file tree
Hide file tree
Showing 20 changed files with 302 additions and 137 deletions.
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ module.exports = {
'/lib/',
'/esm/',
'/examples/simple/',
'/packages/create-react-admin/templates',
],
transformIgnorePatterns: [
'[/\\\\]node_modules[/\\\\](?!(@hookform)/).+\\.(js|jsx|mjs|ts|tsx)$',
Expand Down
6 changes: 5 additions & 1 deletion packages/create-react-admin/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,11 @@ export default function App(props: Props) {
...InitialProjectConfiguration,
dataProvider: props.dataProvider,
authProvider: props.authProvider,
resources: props.resources?.includes('skip') ? [] : props.resources,
resources: props.resources?.includes('skip')
? []
: props.dataProvider === 'ra-data-fakerest'
? ['posts', 'comments']
: props.resources,
installer: props.install,
name: sanitizedName,
};
Expand Down
47 changes: 47 additions & 0 deletions packages/create-react-admin/src/generateAppFile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import path from 'path';
import fs from 'fs';
import { ProjectConfiguration } from './ProjectState';

export const generateAppFile = (
projectDirectory: string,
state: ProjectConfiguration
) => {
fs.writeFileSync(
path.join(projectDirectory, 'src', 'App.tsx'),
`
import { Admin, Resource, ListGuesser, EditGuesser, ShowGuesser } from 'react-admin';
import { Layout } from './Layout';
${
state.dataProvider !== 'none'
? `import { dataProvider } from './dataProvider';\n`
: ''
}${
state.authProvider !== 'none'
? `import { authProvider } from './authProvider';\n`
: ''
}
export const App = () => (
<Admin
layout={Layout}
${
state.dataProvider !== 'none'
? `dataProvider={dataProvider}\n\t`
: ''
}${
state.authProvider !== 'none'
? `\tauthProvider={authProvider}\n\t`
: ''
}>
${state.resources
.map(
resource =>
`<Resource name="${resource}" list={ListGuesser} edit={EditGuesser} show={ShowGuesser} />`
)
.join('\n\t\t')}
</Admin>
);
`
);
};
56 changes: 56 additions & 0 deletions packages/create-react-admin/src/generateAppTestFile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import path from 'path';
import fs from 'fs';
import { ProjectConfiguration } from './ProjectState';

export const generateAppTestFile = (
projectDirectory: string,
state: ProjectConfiguration
) => {
fs.writeFileSync(
path.join(projectDirectory, 'src', 'App.spec.tsx'),
`
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { App } from "./App";
test("should pass", async () => {
vi.spyOn(window, "scrollTo").mockImplementation(() => {});
render(<App />);
${
state.authProvider !== 'none'
? `
// Sign in
fireEvent.change(await screen.findByLabelText("Username *"), {
target: { value: "janedoe" },
});
fireEvent.change(await screen.findByLabelText("Password *"), {
target: { value: "password" },
});
fireEvent.click(await screen.findByText("Sign in"));`
: ''
}
// Open the first post
fireEvent.click(await screen.findByText("Post 1"));
// Update its title
fireEvent.change(await screen.findByDisplayValue("Post 1"), {
target: { value: "Post 1 edited" },
});
fireEvent.click(await screen.findByText("Save"));
await screen.findByText("Post 1 edited");
// Navigate to the comments
fireEvent.click(await screen.findByText("Comments"));
// Open the first comment
fireEvent.click(await screen.findByText("Comment 1"));
// Edit the comment selected post
fireEvent.click(await screen.findByDisplayValue("#0"));
fireEvent.click(await screen.findByText("#11"));
fireEvent.click(await screen.findByText("Save"));
// Check the comment has been updated by finding the post link in the comments list page
await screen.findByText("#11", { selector: "a *" });
}, 10000);
`
);
};
161 changes: 31 additions & 130 deletions packages/create-react-admin/src/generateProject.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import path from 'path';
import fs from 'fs';
import fsExtra from 'fs-extra';
import merge from 'lodash/merge';
import { ProjectConfiguration } from './ProjectState.js';
import { generateAppFile } from './generateAppFile.js';
import { generateAppTestFile } from './generateAppTestFile.js';

export const generateProject = async (state: ProjectConfiguration) => {
const projectDirectory = initializeProjectDirectory(state.name);
Expand All @@ -19,20 +22,29 @@ export const generateProject = async (state: ProjectConfiguration) => {
if (state.dataProvider !== 'none') {
copyDirectoryFiles(
path.join(__dirname, '../templates', state.dataProvider),
path.join(path.join(projectDirectory, 'src')),
['package.json', '.env', 'README.md', 'help.txt']
projectDirectory,
['package.json', '.env', 'README.md', 'help.txt', 'gitignore']
);
}

if (state.authProvider !== 'none') {
copyDirectoryFiles(
path.join(__dirname, '../templates', state.authProvider),
path.join(path.join(projectDirectory, 'src')),
['package.json', '.env', 'README.md', 'help.txt']
projectDirectory,
['package.json', '.env', 'README.md', 'help.txt', 'gitignore']
);
}

generateAppFile(projectDirectory, state);
if (
state.dataProvider === 'ra-data-fakerest' &&
['posts', 'comments'].every(resource =>
state.resources.includes(resource)
)
) {
generateAppTestFile(projectDirectory, state);
}

generatePackageJson(projectDirectory, state);
generateGitIgnore(projectDirectory);
generateEnvFile(projectDirectory, state);
Expand Down Expand Up @@ -66,74 +78,21 @@ const getTemplateHelpMessages = (template: string) => {
return '';
};

const generateAppFile = (
projectDirectory: string,
state: ProjectConfiguration
) => {
fs.writeFileSync(
path.join(projectDirectory, 'src', 'App.tsx'),
`
import { Admin, Resource, ListGuesser, EditGuesser, ShowGuesser } from 'react-admin';
import { Layout } from './Layout';
${
state.dataProvider !== 'none'
? `import { dataProvider } from './dataProvider';\n`
: ''
}${
state.authProvider !== 'none'
? `import { authProvider } from './authProvider';\n`
: ''
}
export const App = () => (
<Admin
layout={Layout}
${
state.dataProvider !== 'none'
? `dataProvider={dataProvider}\n\t`
: ''
}${
state.authProvider !== 'none'
? `\tauthProvider={authProvider}\n\t`
: ''
}>
${state.resources
.map(
resource =>
`<Resource name="${resource}" list={ListGuesser} edit={EditGuesser} show={ShowGuesser} />`
)
.join('\n\t\t')}
</Admin>
);
`
);
};

const generatePackageJson = (
projectDirectory: string,
state: ProjectConfiguration
) => {
const dataProviderDeps = getTemplateDependencies(state.dataProvider);
const authProviderDeps = getTemplateDependencies(state.authProvider);
const allDeps = {
...BasePackageJson.dependencies,
...dataProviderDeps,
...authProviderDeps,
};
const allDepsNames = Object.keys(allDeps).sort();
const dependencies = allDepsNames.reduce(
(acc, depName) => ({
...acc,
[depName]: allDeps[depName],
}),
{}
const basePackageJson = getTemplatePackageJson('common');
const dataProviderPackageJson = getTemplatePackageJson(state.dataProvider);
const authProviderPackageJson = getTemplatePackageJson(state.authProvider);
const packageJson = merge(
basePackageJson,
dataProviderPackageJson,
authProviderPackageJson,
{
name: state.name,
}
);
const packageJson = {
name: state.name,
...BasePackageJson,
dependencies,
};

fs.writeFileSync(
path.join(projectDirectory, 'package.json'),
Expand All @@ -142,9 +101,9 @@ const generatePackageJson = (
};

const generateGitIgnore = (projectDirectory: string) => {
fs.writeFileSync(
path.join(projectDirectory, '.gitignore'),
defaultGitIgnore
fs.copyFileSync(
path.join(__dirname, '../templates/common/gitignore'),
path.join(projectDirectory, '.gitignore')
);
};

Expand Down Expand Up @@ -182,39 +141,7 @@ const getTemplateEnv = (template: string) => {
return undefined;
};

const BasePackageJson = {
private: true,
scripts: {
dev: 'vite',
build: 'vite build',
serve: 'vite preview',
'type-check': 'tsc --noEmit',
lint: 'eslint --fix --ext .js,.jsx,.ts,.tsx ./src',
format: 'prettier --write ./src',
},
dependencies: {
react: '^18.2.0',
'react-admin': '^4.16.0',
'react-dom': '^18.2.0',
},
devDependencies: {
'@typescript-eslint/parser': '^5.60.1',
'@typescript-eslint/eslint-plugin': '^5.60.1',
'@types/node': '^20.10.7',
'@types/react': '^18.0.22',
'@types/react-dom': '^18.0.7',
'@vitejs/plugin-react': '^4.0.1',
eslint: '^8.43.0',
'eslint-config-prettier': '^8.8.0',
'eslint-plugin-react': '^7.32.2',
'eslint-plugin-react-hooks': '^4.6.0',
prettier: '^2.8.8',
typescript: '^5.1.6',
vite: '^4.3.9',
},
};

const getTemplateDependencies = (template: string) => {
const getTemplatePackageJson = (template: string) => {
if (template === 'none' || template === '') {
return {};
}
Expand All @@ -226,7 +153,7 @@ const getTemplateDependencies = (template: string) => {
);
if (fs.existsSync(packageJsonPath)) {
const packageJson = fs.readFileSync(packageJsonPath, 'utf-8');
return JSON.parse(packageJson).dependencies;
return JSON.parse(packageJson);
}
return {};
};
Expand Down Expand Up @@ -344,29 +271,3 @@ const replaceTokensInFile = (filePath: string, state: ProjectConfiguration) => {
fileContent = replaceTokens(fileContent, state);
fs.writeFileSync(filePath, fileContent);
};

const defaultGitIgnore = `# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
`;
5 changes: 3 additions & 2 deletions packages/create-react-admin/templates/common/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module.exports = {
export default {
"extends": [
"eslint:recommended",
"plugin:react/recommended",
Expand All @@ -10,7 +10,8 @@ module.exports = {
"plugins": ["@typescript-eslint"],
"env": {
"browser": true,
"es2021": true
"es2021": true,
"node": true
},
"settings": {
"react": {
Expand Down
24 changes: 24 additions & 0 deletions packages/create-react-admin/templates/common/gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
Loading

0 comments on commit a2f273f

Please sign in to comment.