Fix types and login logo, remove unneeded files
This commit is contained in:
parent
8acda23acc
commit
0fd69879fa
@ -71,10 +71,9 @@ Simple CMS uses the [Forking Workflow](https://www.atlassian.com/git/tutorials/c
|
|||||||
2. Create a branch from `main`. If you're addressing a specific issue, prefix your branch name with the issue number.
|
2. Create a branch from `main`. If you're addressing a specific issue, prefix your branch name with the issue number.
|
||||||
3. If you've added code that should be tested, add tests.
|
3. If you've added code that should be tested, add tests.
|
||||||
4. If you've changed APIs, update the documentation.
|
4. If you've changed APIs, update the documentation.
|
||||||
5. Run `yarn test` and ensure the test suite passes.
|
5. Use `yarn format` to format and lint your code.
|
||||||
6. Use `yarn format` to format and lint your code.
|
6. PR's must be rebased before merge (feel free to ask for help).
|
||||||
7. PR's must be rebased before merge (feel free to ask for help).
|
7. PR should be reviewed by two maintainers prior to merging.
|
||||||
8. PR should be reviewed by two maintainers prior to merging.
|
|
||||||
|
|
||||||
## Debugging
|
## Debugging
|
||||||
|
|
||||||
@ -105,34 +104,6 @@ When debugging the CMS with Git Gateway you must:
|
|||||||
4. Refresh the page
|
4. Refresh the page
|
||||||
5. You should be able to log in via your Netlify Identity email/password
|
5. You should be able to log in via your Netlify Identity email/password
|
||||||
|
|
||||||
### Fine tune the way you run unit tests
|
|
||||||
|
|
||||||
There are situations where you would want to run a specific test file, or tests that match a certain pattern.
|
|
||||||
|
|
||||||
To run all the tests for a specific file, use this command:
|
|
||||||
|
|
||||||
```
|
|
||||||
yarn jest <filename or file path>
|
|
||||||
```
|
|
||||||
|
|
||||||
The first part of the command, `yarn jest` means running the locally installed version of `jest`. It is equivalent to running `node_modules/.bin/jest`.
|
|
||||||
|
|
||||||
Example for running all the tests for the file `gitlab.spec.js`: `yarn jest gitlab.spec.js`
|
|
||||||
|
|
||||||
Example for running all the tests for the file `API.spec.js` in the `gitlab` package:
|
|
||||||
|
|
||||||
`yarn jest ".+backend-gitlab/.+/API.spec.js`
|
|
||||||
|
|
||||||
To run a specific test in a file, add the flag `--testNamePattern`, or `-t` for short followed by a regexp to match your test name.
|
|
||||||
|
|
||||||
Example for running the test "should return true on project access_level >= 30" in the API.spec.js in `gitlab` package:
|
|
||||||
|
|
||||||
```
|
|
||||||
yarn jest -t "true on p" ".+backend-gitlab/.+/API.spec.js"
|
|
||||||
```
|
|
||||||
|
|
||||||
For more information about running tests exactly the way you want, check out the official documentation for [Jest CLI](https://jestjs.io/docs/cli).
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
By contributing to Simple CMS, you agree that your contributions will be licensed under its [MIT license](LICENSE).
|
By contributing to Simple CMS, you agree that your contributions will be licensed under its [MIT license](LICENSE).
|
||||||
|
@ -125,4 +125,8 @@ Every release is documented on the Github [Releases](https://github.com/SimpleCM
|
|||||||
Simple CMS is released under the [MIT License](LICENSE).
|
Simple CMS is released under the [MIT License](LICENSE).
|
||||||
Please make sure you understand its [implications and guarantees](https://writing.kemitchell.com/2016/09/21/MIT-License-Line-by-Line.html).
|
Please make sure you understand its [implications and guarantees](https://writing.kemitchell.com/2016/09/21/MIT-License-Line-by-Line.html).
|
||||||
|
|
||||||
|
# Netlify CMS
|
||||||
|
|
||||||
|
Simple CMS is a fork of Netlify CMS focusing on the core product over adding massive new features.
|
||||||
|
|
||||||
# Thanks
|
# Thanks
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"baseUrl": "http://localhost:8080",
|
|
||||||
"projectId": "dzqjxb",
|
|
||||||
"testFiles": "*spec*.js",
|
|
||||||
"retries": {
|
|
||||||
"runMode": 2,
|
|
||||||
"openMode": 0
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,84 +0,0 @@
|
|||||||
const crypto = require('crypto');
|
|
||||||
const axios = require('axios');
|
|
||||||
|
|
||||||
const verifySignature = event => {
|
|
||||||
const timestamp = Number(event.headers['x-slack-request-timestamp']);
|
|
||||||
const time = Math.floor(Date.now() / 1000);
|
|
||||||
if (time - timestamp > 60 * 5) {
|
|
||||||
throw new Error(`Failed verifying signature. Timestamp too old '${timestamp}'`);
|
|
||||||
}
|
|
||||||
const body = event.body;
|
|
||||||
const sigString = `v0:${timestamp}:${body}`;
|
|
||||||
const actualSignature = event.headers['x-slack-signature'];
|
|
||||||
const secret = process.env.SLACK_SIGNING_SECRET;
|
|
||||||
|
|
||||||
const hash = crypto
|
|
||||||
.createHmac('sha256', secret)
|
|
||||||
.update(sigString, 'utf8')
|
|
||||||
.digest('hex');
|
|
||||||
|
|
||||||
const expectedSignature = `v0=${hash}`;
|
|
||||||
|
|
||||||
const signaturesMatch = crypto.timingSafeEqual(
|
|
||||||
Buffer.from(actualSignature, 'utf8'),
|
|
||||||
Buffer.from(expectedSignature, 'utf8'),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!signaturesMatch) {
|
|
||||||
throw new Error(
|
|
||||||
`Signatures don't match. Expected: '${expectedSignature}', actual: '${actualSignature}'`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.handler = async function(event) {
|
|
||||||
try {
|
|
||||||
console.info(
|
|
||||||
JSON.stringify(
|
|
||||||
{
|
|
||||||
event,
|
|
||||||
env: {
|
|
||||||
PUBLISH_COMMAND: process.env.PUBLISH_COMMAND,
|
|
||||||
ALLOWED_USERS: process.env.ALLOWED_USERS,
|
|
||||||
GITHUB_REPO: process.env.GITHUB_REPO,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
2,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
verifySignature(event);
|
|
||||||
|
|
||||||
const params = new URLSearchParams(event.body);
|
|
||||||
const command = params.get('command');
|
|
||||||
const userId = params.get('user_id');
|
|
||||||
|
|
||||||
const allowedUsers = (process.env.ALLOWED_USERS || '').split(',');
|
|
||||||
if (!allowedUsers.includes(userId)) {
|
|
||||||
throw new Error(`User '${params.get('user_name')}' is not allowed to run command`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const expectedCommand = process.env.PUBLISH_COMMAND;
|
|
||||||
if (expectedCommand && expectedCommand == command) {
|
|
||||||
const githubToken = process.env.GITHUB_TOKEN;
|
|
||||||
const repo = process.env.GITHUB_REPO;
|
|
||||||
await axios({
|
|
||||||
headers: { Authorization: `token ${githubToken}` },
|
|
||||||
method: 'post',
|
|
||||||
url: `https://api.github.com/repos/${repo}/dispatches`,
|
|
||||||
data: { event_type: 'on-demand-github-action' },
|
|
||||||
});
|
|
||||||
const message = 'Dispatched event to GitHub';
|
|
||||||
return { status: 200, body: message };
|
|
||||||
} else {
|
|
||||||
throw new Error(`Command is not allowed. Expected: ${expectedCommand}. Actual: ${command}`);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.info(e);
|
|
||||||
const response = {
|
|
||||||
body: 'Unauthorized',
|
|
||||||
status: 401,
|
|
||||||
};
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
};
|
|
Binary file not shown.
Before Width: | Height: | Size: 97 KiB |
Binary file not shown.
Before Width: | Height: | Size: 14 KiB |
BIN
img/travis.png
BIN
img/travis.png
Binary file not shown.
Before Width: | Height: | Size: 91 KiB |
2
index.d.ts
vendored
2
index.d.ts
vendored
@ -1,5 +1,5 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
declare module 'simple-cms-core' {
|
declare module '@simplecms/simple-cms-core' {
|
||||||
import type { Iterable as ImmutableIterable, List, Map } from 'immutable';
|
import type { Iterable as ImmutableIterable, List, Map } from 'immutable';
|
||||||
import type { ComponentType, FocusEventHandler, ReactNode } from 'react';
|
import type { ComponentType, FocusEventHandler, ReactNode } from 'react';
|
||||||
import type { t } from 'react-polyglot';
|
import type { t } from 'react-polyglot';
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
setupFilesAfterEnv: ['<rootDir>/setupTestFramework.js'],
|
|
||||||
moduleNameMapper: {
|
|
||||||
'\\.(css|less)$': '<rootDir>/__mocks__/styleMock.js',
|
|
||||||
},
|
|
||||||
testURL: 'http://localhost:8080',
|
|
||||||
snapshotSerializers: ['jest-emotion'],
|
|
||||||
transformIgnorePatterns: [
|
|
||||||
'node_modules/(?!copy-text-to-clipboard|clean-stack|escape-string-regexp)',
|
|
||||||
],
|
|
||||||
testEnvironment: 'jsdom',
|
|
||||||
};
|
|
17
package.json
17
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@simplecms/simple-cms-core",
|
"name": "@simplecms/simple-cms-core",
|
||||||
"version": "0.1.0",
|
"version": "0.1.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"description": "Simple CMS core application.",
|
"description": "Simple CMS core application.",
|
||||||
"repository": "https://github.com/SimpleCMS/simple-cms",
|
"repository": "https://github.com/SimpleCMS/simple-cms",
|
||||||
@ -169,12 +169,8 @@
|
|||||||
"@emotion/eslint-plugin": "11.10.0",
|
"@emotion/eslint-plugin": "11.10.0",
|
||||||
"@octokit/rest": "16.43.2",
|
"@octokit/rest": "16.43.2",
|
||||||
"@stylelint/postcss-css-in-js": "0.37.3",
|
"@stylelint/postcss-css-in-js": "0.37.3",
|
||||||
"@testing-library/dom": "8.18.1",
|
|
||||||
"@testing-library/jest-dom": "5.16.5",
|
|
||||||
"@testing-library/react": "12.1.5",
|
|
||||||
"@types/common-tags": "1.8.0",
|
"@types/common-tags": "1.8.0",
|
||||||
"@types/history": "4.7.11",
|
"@types/history": "4.7.11",
|
||||||
"@types/jest": "27.5.2",
|
|
||||||
"@types/js-base64": "3.3.1",
|
"@types/js-base64": "3.3.1",
|
||||||
"@types/jwt-decode": "2.2.1",
|
"@types/jwt-decode": "2.2.1",
|
||||||
"@types/lodash": "4.14.185",
|
"@types/lodash": "4.14.185",
|
||||||
@ -189,7 +185,6 @@
|
|||||||
"axios": "0.26.1",
|
"axios": "0.26.1",
|
||||||
"babel-core": "7.0.0-bridge.0",
|
"babel-core": "7.0.0-bridge.0",
|
||||||
"babel-eslint": "11.0.0-beta.2",
|
"babel-eslint": "11.0.0-beta.2",
|
||||||
"babel-jest": "27.5.1",
|
|
||||||
"babel-loader": "8.2.5",
|
"babel-loader": "8.2.5",
|
||||||
"babel-plugin-emotion": "11.0.0",
|
"babel-plugin-emotion": "11.0.0",
|
||||||
"babel-plugin-inline-json-import": "0.3.2",
|
"babel-plugin-inline-json-import": "0.3.2",
|
||||||
@ -204,11 +199,6 @@
|
|||||||
"commonmark-spec": "0.30.0",
|
"commonmark-spec": "0.30.0",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"css-loader": "3.6.0",
|
"css-loader": "3.6.0",
|
||||||
"cypress": "9.5.3",
|
|
||||||
"cypress-file-upload": "5.0.8",
|
|
||||||
"cypress-image-snapshot": "4.0.1",
|
|
||||||
"cypress-jest-adapter": "0.1.1",
|
|
||||||
"cypress-plugin-tab": "1.0.5",
|
|
||||||
"dotenv": "10.0.0",
|
"dotenv": "10.0.0",
|
||||||
"eslint": "8.24.0",
|
"eslint": "8.24.0",
|
||||||
"eslint-plugin-cypress": "2.12.1",
|
"eslint-plugin-cypress": "2.12.1",
|
||||||
@ -220,9 +210,6 @@
|
|||||||
"fs-extra": "10.1.0",
|
"fs-extra": "10.1.0",
|
||||||
"gitlab": "14.2.2",
|
"gitlab": "14.2.2",
|
||||||
"http-server": "14.1.1",
|
"http-server": "14.1.1",
|
||||||
"jest": "27.5.1",
|
|
||||||
"jest-cli": "27.5.1",
|
|
||||||
"jest-emotion": "11.0.0",
|
|
||||||
"js-yaml": "4.1.0",
|
"js-yaml": "4.1.0",
|
||||||
"mockserver-client": "5.14.0",
|
"mockserver-client": "5.14.0",
|
||||||
"mockserver-node": "5.14.0",
|
"mockserver-node": "5.14.0",
|
||||||
@ -234,13 +221,11 @@
|
|||||||
"postcss-scss": "4.0.5",
|
"postcss-scss": "4.0.5",
|
||||||
"prettier": "2.7.1",
|
"prettier": "2.7.1",
|
||||||
"react-svg-loader": "3.0.3",
|
"react-svg-loader": "3.0.3",
|
||||||
"react-test-renderer": "16.14.0",
|
|
||||||
"rehype": "7.0.0",
|
"rehype": "7.0.0",
|
||||||
"rimraf": "3.0.2",
|
"rimraf": "3.0.2",
|
||||||
"simple-git": "3.14.1",
|
"simple-git": "3.14.1",
|
||||||
"slate-hyperscript": "0.13.9",
|
"slate-hyperscript": "0.13.9",
|
||||||
"source-map-loader": "^4.0.0",
|
"source-map-loader": "^4.0.0",
|
||||||
"start-server-and-test": "1.14.0",
|
|
||||||
"stylelint": "14.12.1",
|
"stylelint": "14.12.1",
|
||||||
"stylelint-config-standard-scss": "3.0.0",
|
"stylelint-config-standard-scss": "3.0.0",
|
||||||
"stylelint-config-styled-components": "0.1.1",
|
"stylelint-config-styled-components": "0.1.1",
|
||||||
|
@ -1,45 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { render } from '@testing-library/react';
|
|
||||||
|
|
||||||
import { CopyToClipBoardButton } from '../MediaLibraryButtons';
|
|
||||||
|
|
||||||
describe('CopyToClipBoardButton', () => {
|
|
||||||
const props = {
|
|
||||||
disabled: false,
|
|
||||||
t: jest.fn(key => key),
|
|
||||||
};
|
|
||||||
|
|
||||||
it('should use copy text when no path is defined', () => {
|
|
||||||
const { container } = render(<CopyToClipBoardButton {...props} />);
|
|
||||||
|
|
||||||
expect(container).toHaveTextContent('mediaLibrary.mediaLibraryCard.copy');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should use copyUrl text when path is absolute and is draft', () => {
|
|
||||||
const { container } = render(
|
|
||||||
<CopyToClipBoardButton {...props} path="https://www.images.com/image.png" draft />,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(container).toHaveTextContent('mediaLibrary.mediaLibraryCard.copyUrl');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should use copyUrl text when path is absolute and is not draft', () => {
|
|
||||||
const { container } = render(
|
|
||||||
<CopyToClipBoardButton {...props} path="https://www.images.com/image.png" />,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(container).toHaveTextContent('mediaLibrary.mediaLibraryCard.copyUrl');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should use copyName when path is not absolute and is draft', () => {
|
|
||||||
const { container } = render(<CopyToClipBoardButton {...props} path="image.png" draft />);
|
|
||||||
|
|
||||||
expect(container).toHaveTextContent('mediaLibrary.mediaLibraryCard.copyName');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should use copyPath when path is not absolute and is not draft', () => {
|
|
||||||
const { container } = render(<CopyToClipBoardButton {...props} path="image.png" />);
|
|
||||||
|
|
||||||
expect(container).toHaveTextContent('mediaLibrary.mediaLibraryCard.copyPath');
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,49 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Map } from 'immutable';
|
|
||||||
import { render } from '@testing-library/react';
|
|
||||||
|
|
||||||
import MediaLibraryCard from '../MediaLibraryCard';
|
|
||||||
|
|
||||||
describe('MediaLibraryCard', () => {
|
|
||||||
const props = {
|
|
||||||
displayURL: Map({ url: 'url' }),
|
|
||||||
text: 'image.png',
|
|
||||||
onClick: jest.fn(),
|
|
||||||
draftText: 'Draft',
|
|
||||||
width: '100px',
|
|
||||||
height: '240px',
|
|
||||||
margin: '10px',
|
|
||||||
isViewableImage: true,
|
|
||||||
loadDisplayURL: jest.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
it('should match snapshot for non draft image', () => {
|
|
||||||
const { asFragment, queryByTestId } = render(<MediaLibraryCard {...props} />);
|
|
||||||
|
|
||||||
expect(queryByTestId('draft-text')).toBeNull();
|
|
||||||
expect(asFragment()).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should match snapshot for draft image', () => {
|
|
||||||
const { asFragment, getByTestId } = render(<MediaLibraryCard {...props} isDraft={true} />);
|
|
||||||
expect(getByTestId('draft-text')).toHaveTextContent('Draft');
|
|
||||||
expect(asFragment()).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should match snapshot for non viewable image', () => {
|
|
||||||
const { asFragment, getByTestId } = render(
|
|
||||||
<MediaLibraryCard {...props} isViewableImage={false} type="Not Viewable" />,
|
|
||||||
);
|
|
||||||
expect(getByTestId('card-file-icon')).toHaveTextContent('Not Viewable');
|
|
||||||
expect(asFragment()).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call loadDisplayURL on mount when url is empty', () => {
|
|
||||||
const loadDisplayURL = jest.fn();
|
|
||||||
render(
|
|
||||||
<MediaLibraryCard {...props} loadDisplayURL={loadDisplayURL} displayURL={Map({ url: '' })} />,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(loadDisplayURL).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,214 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`MediaLibraryCard should match snapshot for draft image 1`] = `
|
|
||||||
<DocumentFragment>
|
|
||||||
.emotion-8 {
|
|
||||||
width: 100px;
|
|
||||||
height: 240px;
|
|
||||||
margin: 10px;
|
|
||||||
border: solid 2px #dfdfe3;
|
|
||||||
border-radius: 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.emotion-8:focus {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.emotion-4 {
|
|
||||||
height: 162px;
|
|
||||||
background-color: #f2f2f2;
|
|
||||||
background-size: 16px 16px;
|
|
||||||
background-position: 0 0,8px 8px;
|
|
||||||
background-image: linear-gradient( 45deg, #e6e6e6 25%, transparent 25%, transparent 75%, #e6e6e6 75%, #e6e6e6 ) , linear-gradient( 45deg, #e6e6e6 25%, transparent 25%, transparent 75%, #e6e6e6 75%, #e6e6e6 );
|
|
||||||
box-shadow: inset 0 0 4px rgba(68,74,87,0.3);
|
|
||||||
border-bottom: solid 2px #dfdfe3;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.emotion-2 {
|
|
||||||
width: 100%;
|
|
||||||
height: 160px;
|
|
||||||
object-fit: contain;
|
|
||||||
border-radius: 2px 2px 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.emotion-6 {
|
|
||||||
color: #798291;
|
|
||||||
padding: 8px;
|
|
||||||
margin-top: 20px;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
line-height: 1.3 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.emotion-0 {
|
|
||||||
color: #70399f;
|
|
||||||
background-color: #f6d8ff;
|
|
||||||
position: absolute;
|
|
||||||
padding: 8px;
|
|
||||||
border-radius: 5px 0 5px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="emotion-8 emotion-9"
|
|
||||||
height="240px"
|
|
||||||
tabindex="-1"
|
|
||||||
width="100px"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="emotion-4 emotion-5"
|
|
||||||
>
|
|
||||||
<p
|
|
||||||
class="emotion-0 emotion-1"
|
|
||||||
data-testid="draft-text"
|
|
||||||
>
|
|
||||||
Draft
|
|
||||||
</p>
|
|
||||||
<img
|
|
||||||
class="emotion-2 emotion-3"
|
|
||||||
src="url"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<p
|
|
||||||
class="emotion-6 emotion-7"
|
|
||||||
>
|
|
||||||
image.png
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</DocumentFragment>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`MediaLibraryCard should match snapshot for non draft image 1`] = `
|
|
||||||
<DocumentFragment>
|
|
||||||
.emotion-6 {
|
|
||||||
width: 100px;
|
|
||||||
height: 240px;
|
|
||||||
margin: 10px;
|
|
||||||
border: solid 2px #dfdfe3;
|
|
||||||
border-radius: 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.emotion-6:focus {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.emotion-2 {
|
|
||||||
height: 162px;
|
|
||||||
background-color: #f2f2f2;
|
|
||||||
background-size: 16px 16px;
|
|
||||||
background-position: 0 0,8px 8px;
|
|
||||||
background-image: linear-gradient( 45deg, #e6e6e6 25%, transparent 25%, transparent 75%, #e6e6e6 75%, #e6e6e6 ) , linear-gradient( 45deg, #e6e6e6 25%, transparent 25%, transparent 75%, #e6e6e6 75%, #e6e6e6 );
|
|
||||||
box-shadow: inset 0 0 4px rgba(68,74,87,0.3);
|
|
||||||
border-bottom: solid 2px #dfdfe3;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.emotion-0 {
|
|
||||||
width: 100%;
|
|
||||||
height: 160px;
|
|
||||||
object-fit: contain;
|
|
||||||
border-radius: 2px 2px 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.emotion-4 {
|
|
||||||
color: #798291;
|
|
||||||
padding: 8px;
|
|
||||||
margin-top: 20px;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
line-height: 1.3 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="emotion-6 emotion-7"
|
|
||||||
height="240px"
|
|
||||||
tabindex="-1"
|
|
||||||
width="100px"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="emotion-2 emotion-3"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
class="emotion-0 emotion-1"
|
|
||||||
src="url"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<p
|
|
||||||
class="emotion-4 emotion-5"
|
|
||||||
>
|
|
||||||
image.png
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</DocumentFragment>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`MediaLibraryCard should match snapshot for non viewable image 1`] = `
|
|
||||||
<DocumentFragment>
|
|
||||||
.emotion-6 {
|
|
||||||
width: 100px;
|
|
||||||
height: 240px;
|
|
||||||
margin: 10px;
|
|
||||||
border: solid 2px #dfdfe3;
|
|
||||||
border-radius: 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.emotion-6:focus {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.emotion-2 {
|
|
||||||
height: 162px;
|
|
||||||
background-color: #f2f2f2;
|
|
||||||
background-size: 16px 16px;
|
|
||||||
background-position: 0 0,8px 8px;
|
|
||||||
background-image: linear-gradient( 45deg, #e6e6e6 25%, transparent 25%, transparent 75%, #e6e6e6 75%, #e6e6e6 ) , linear-gradient( 45deg, #e6e6e6 25%, transparent 25%, transparent 75%, #e6e6e6 75%, #e6e6e6 );
|
|
||||||
box-shadow: inset 0 0 4px rgba(68,74,87,0.3);
|
|
||||||
border-bottom: solid 2px #dfdfe3;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.emotion-4 {
|
|
||||||
color: #798291;
|
|
||||||
padding: 8px;
|
|
||||||
margin-top: 20px;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
line-height: 1.3 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.emotion-0 {
|
|
||||||
width: 100%;
|
|
||||||
height: 160px;
|
|
||||||
object-fit: cover;
|
|
||||||
border-radius: 2px 2px 0 0;
|
|
||||||
padding: 1em;
|
|
||||||
font-size: 3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="emotion-6 emotion-7"
|
|
||||||
height="240px"
|
|
||||||
tabindex="-1"
|
|
||||||
width="100px"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="emotion-2 emotion-3"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="emotion-0 emotion-1"
|
|
||||||
data-testid="card-file-icon"
|
|
||||||
>
|
|
||||||
Not Viewable
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p
|
|
||||||
class="emotion-4 emotion-5"
|
|
||||||
>
|
|
||||||
image.png
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</DocumentFragment>
|
|
||||||
`;
|
|
@ -1,511 +0,0 @@
|
|||||||
import { merge } from 'lodash';
|
|
||||||
|
|
||||||
import { validateConfig } from '../configSchema';
|
|
||||||
|
|
||||||
jest.mock('../../lib/registry');
|
|
||||||
|
|
||||||
describe('config', () => {
|
|
||||||
/**
|
|
||||||
* Suppress error logging to reduce noise during testing. Jest will still
|
|
||||||
* log test failures and associated errors as expected.
|
|
||||||
*/
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.spyOn(console, 'error').mockImplementation(() => {});
|
|
||||||
});
|
|
||||||
|
|
||||||
const { getWidgets } = require('../../lib/registry');
|
|
||||||
getWidgets.mockImplementation(() => [{}]);
|
|
||||||
|
|
||||||
describe('validateConfig', () => {
|
|
||||||
const validConfig = {
|
|
||||||
foo: 'bar',
|
|
||||||
backend: { name: 'bar' },
|
|
||||||
media_folder: 'baz',
|
|
||||||
collections: [
|
|
||||||
{
|
|
||||||
name: 'posts',
|
|
||||||
label: 'Posts',
|
|
||||||
folder: '_posts',
|
|
||||||
fields: [{ name: 'title', label: 'title', widget: 'string' }],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
it('should not throw if no errors', () => {
|
|
||||||
expect(() => {
|
|
||||||
validateConfig(validConfig);
|
|
||||||
}).not.toThrowError();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw if backend is not defined in config', () => {
|
|
||||||
expect(() => {
|
|
||||||
validateConfig({ foo: 'bar' });
|
|
||||||
}).toThrowError("config must have required property 'backend'");
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw if backend name is not defined in config', () => {
|
|
||||||
expect(() => {
|
|
||||||
validateConfig({ foo: 'bar', backend: {} });
|
|
||||||
}).toThrowError("'backend' must have required property 'name'");
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw if backend name is not a string in config', () => {
|
|
||||||
expect(() => {
|
|
||||||
validateConfig({ foo: 'bar', backend: { name: {} } });
|
|
||||||
}).toThrowError("'backend.name' must be string");
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw if backend.open_authoring is not a boolean in config', () => {
|
|
||||||
expect(() => {
|
|
||||||
validateConfig(merge(validConfig, { backend: { open_authoring: 'true' } }));
|
|
||||||
}).toThrowError("'backend.open_authoring' must be boolean");
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not throw if backend.open_authoring is boolean in config', () => {
|
|
||||||
expect(() => {
|
|
||||||
validateConfig(merge(validConfig, { backend: { open_authoring: true } }));
|
|
||||||
}).not.toThrowError();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw if backend.auth_scope is not "repo" or "public_repo" in config', () => {
|
|
||||||
expect(() => {
|
|
||||||
validateConfig(merge(validConfig, { backend: { auth_scope: 'user' } }));
|
|
||||||
}).toThrowError("'backend.auth_scope' must be equal to one of the allowed values");
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not throw if backend.auth_scope is one of "repo" or "public_repo" in config', () => {
|
|
||||||
expect(() => {
|
|
||||||
validateConfig(merge(validConfig, { backend: { auth_scope: 'repo' } }));
|
|
||||||
}).not.toThrowError();
|
|
||||||
expect(() => {
|
|
||||||
validateConfig(merge(validConfig, { backend: { auth_scope: 'public_repo' } }));
|
|
||||||
}).not.toThrowError();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw if media_folder is not defined in config', () => {
|
|
||||||
expect(() => {
|
|
||||||
validateConfig({ foo: 'bar', backend: { name: 'bar' } });
|
|
||||||
}).toThrowError("config must have required property 'media_folder'");
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw if media_folder is not a string in config', () => {
|
|
||||||
expect(() => {
|
|
||||||
validateConfig({ foo: 'bar', backend: { name: 'bar' }, media_folder: {} });
|
|
||||||
}).toThrowError("'media_folder' must be string");
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw if collections is not defined in config', () => {
|
|
||||||
expect(() => {
|
|
||||||
validateConfig({ foo: 'bar', backend: { name: 'bar' }, media_folder: 'baz' });
|
|
||||||
}).toThrowError("config must have required property 'collections'");
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw if collections not an array in config', () => {
|
|
||||||
expect(() => {
|
|
||||||
validateConfig({
|
|
||||||
foo: 'bar',
|
|
||||||
backend: { name: 'bar' },
|
|
||||||
media_folder: 'baz',
|
|
||||||
collections: {},
|
|
||||||
});
|
|
||||||
}).toThrowError("'collections' must be array");
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw if collections is an empty array in config', () => {
|
|
||||||
expect(() => {
|
|
||||||
validateConfig({
|
|
||||||
foo: 'bar',
|
|
||||||
backend: { name: 'bar' },
|
|
||||||
media_folder: 'baz',
|
|
||||||
collections: [],
|
|
||||||
});
|
|
||||||
}).toThrowError("'collections' must NOT have fewer than 1 items");
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw if collections is an array with a single null element in config', () => {
|
|
||||||
expect(() => {
|
|
||||||
validateConfig({
|
|
||||||
foo: 'bar',
|
|
||||||
backend: { name: 'bar' },
|
|
||||||
media_folder: 'baz',
|
|
||||||
collections: [null],
|
|
||||||
});
|
|
||||||
}).toThrowError("'collections[0]' must be object");
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw if local_backend is not a boolean or plain object', () => {
|
|
||||||
expect(() => {
|
|
||||||
validateConfig({ ...validConfig, local_backend: [] });
|
|
||||||
}).toThrowError("'local_backend' must be boolean");
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw if local_backend url is not a string', () => {
|
|
||||||
expect(() => {
|
|
||||||
validateConfig({ ...validConfig, local_backend: { url: [] } });
|
|
||||||
}).toThrowError("'local_backend.url' must be string");
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw if local_backend allowed_hosts is not a string array', () => {
|
|
||||||
expect(() => {
|
|
||||||
validateConfig({ ...validConfig, local_backend: { allowed_hosts: [true] } });
|
|
||||||
}).toThrowError("'local_backend.allowed_hosts[0]' must be string");
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not throw if local_backend is a boolean', () => {
|
|
||||||
expect(() => {
|
|
||||||
validateConfig({ ...validConfig, local_backend: true });
|
|
||||||
}).not.toThrowError();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not throw if local_backend is a plain object with url string property', () => {
|
|
||||||
expect(() => {
|
|
||||||
validateConfig({ ...validConfig, local_backend: { url: 'http://localhost:8081/api/v1' } });
|
|
||||||
}).not.toThrowError();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not throw if local_backend is a plain object with allowed_hosts string array property', () => {
|
|
||||||
expect(() => {
|
|
||||||
validateConfig({
|
|
||||||
...validConfig,
|
|
||||||
local_backend: { allowed_hosts: ['192.168.0.1'] },
|
|
||||||
});
|
|
||||||
}).not.toThrowError();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw if collection publish is not a boolean', () => {
|
|
||||||
expect(() => {
|
|
||||||
validateConfig(merge({}, validConfig, { collections: [{ publish: 'false' }] }));
|
|
||||||
}).toThrowError("'collections[0].publish' must be boolean");
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not throw if collection publish is a boolean', () => {
|
|
||||||
expect(() => {
|
|
||||||
validateConfig(merge({}, validConfig, { collections: [{ publish: false }] }));
|
|
||||||
}).not.toThrowError();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw if collections sortable_fields is not a boolean or a string array', () => {
|
|
||||||
expect(() => {
|
|
||||||
validateConfig(merge({}, validConfig, { collections: [{ sortable_fields: 'title' }] }));
|
|
||||||
}).toThrowError("'collections[0].sortable_fields' must be array");
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should allow sortable_fields to be a string array', () => {
|
|
||||||
expect(() => {
|
|
||||||
validateConfig(merge({}, validConfig, { collections: [{ sortable_fields: ['title'] }] }));
|
|
||||||
}).not.toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should allow sortable_fields to be a an empty array', () => {
|
|
||||||
expect(() => {
|
|
||||||
validateConfig(merge({}, validConfig, { collections: [{ sortable_fields: [] }] }));
|
|
||||||
}).not.toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should allow sortableFields instead of sortable_fields', () => {
|
|
||||||
expect(() => {
|
|
||||||
validateConfig(merge({}, validConfig, { collections: [{ sortableFields: [] }] }));
|
|
||||||
}).not.toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw if both sortable_fields and sortableFields exist', () => {
|
|
||||||
expect(() => {
|
|
||||||
validateConfig(
|
|
||||||
merge({}, validConfig, { collections: [{ sortable_fields: [], sortableFields: [] }] }),
|
|
||||||
);
|
|
||||||
}).toThrowError("'collections[0]' must NOT be valid");
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw if collection names are not unique', () => {
|
|
||||||
expect(() => {
|
|
||||||
validateConfig(
|
|
||||||
merge({}, validConfig, {
|
|
||||||
collections: [validConfig.collections[0], validConfig.collections[0]],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}).toThrowError("'collections' collections names must be unique");
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw if collection file names are not unique', () => {
|
|
||||||
expect(() => {
|
|
||||||
validateConfig(
|
|
||||||
merge({}, validConfig, {
|
|
||||||
collections: [
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
files: [
|
|
||||||
{
|
|
||||||
name: 'a',
|
|
||||||
label: 'a',
|
|
||||||
file: 'a.md',
|
|
||||||
fields: [{ name: 'title', label: 'title', widget: 'string' }],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'a',
|
|
||||||
label: 'b',
|
|
||||||
file: 'b.md',
|
|
||||||
fields: [{ name: 'title', label: 'title', widget: 'string' }],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}).toThrowError("'collections[1].files' files names must be unique");
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw if collection fields names are not unique', () => {
|
|
||||||
expect(() => {
|
|
||||||
validateConfig(
|
|
||||||
merge({}, validConfig, {
|
|
||||||
collections: [
|
|
||||||
{
|
|
||||||
fields: [
|
|
||||||
{ name: 'title', label: 'title', widget: 'string' },
|
|
||||||
{ name: 'title', label: 'other title', widget: 'string' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}).toThrowError("'collections[0].fields' fields names must be unique");
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not throw if collection fields are unique across nesting levels', () => {
|
|
||||||
expect(() => {
|
|
||||||
validateConfig(
|
|
||||||
merge({}, validConfig, {
|
|
||||||
collections: [
|
|
||||||
{
|
|
||||||
fields: [
|
|
||||||
{ name: 'title', label: 'title', widget: 'string' },
|
|
||||||
{
|
|
||||||
name: 'object',
|
|
||||||
label: 'Object',
|
|
||||||
widget: 'object',
|
|
||||||
fields: [{ name: 'title', label: 'title', widget: 'string' }],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}).not.toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('nested validation', () => {
|
|
||||||
const { getWidgets } = require('../../lib/registry');
|
|
||||||
getWidgets.mockImplementation(() => [
|
|
||||||
{
|
|
||||||
name: 'relation',
|
|
||||||
schema: {
|
|
||||||
properties: {
|
|
||||||
search_fields: { type: 'array', items: { type: 'string' } },
|
|
||||||
display_fields: { type: 'array', items: { type: 'string' } },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
it('should throw if nested relation display_fields and search_fields are not arrays', () => {
|
|
||||||
expect(() => {
|
|
||||||
validateConfig(
|
|
||||||
merge({}, validConfig, {
|
|
||||||
collections: [
|
|
||||||
{
|
|
||||||
fields: [
|
|
||||||
{ name: 'title', label: 'title', widget: 'string' },
|
|
||||||
{
|
|
||||||
name: 'object',
|
|
||||||
label: 'Object',
|
|
||||||
widget: 'object',
|
|
||||||
fields: [
|
|
||||||
{ name: 'title', label: 'title', widget: 'string' },
|
|
||||||
{
|
|
||||||
name: 'relation',
|
|
||||||
label: 'relation',
|
|
||||||
widget: 'relation',
|
|
||||||
display_fields: 'title',
|
|
||||||
search_fields: 'title',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}).toThrowError(
|
|
||||||
"'collections[0].fields[1].fields[1].search_fields' must be array\n'collections[0].fields[1].fields[1].display_fields' must be array",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not throw if nested relation display_fields and search_fields are arrays', () => {
|
|
||||||
expect(() => {
|
|
||||||
validateConfig(
|
|
||||||
merge({}, validConfig, {
|
|
||||||
collections: [
|
|
||||||
{
|
|
||||||
fields: [
|
|
||||||
{ name: 'title', label: 'title', widget: 'string' },
|
|
||||||
{
|
|
||||||
name: 'object',
|
|
||||||
label: 'Object',
|
|
||||||
widget: 'object',
|
|
||||||
fields: [
|
|
||||||
{ name: 'title', label: 'title', widget: 'string' },
|
|
||||||
{
|
|
||||||
name: 'relation',
|
|
||||||
label: 'relation',
|
|
||||||
widget: 'relation',
|
|
||||||
display_fields: ['title'],
|
|
||||||
search_fields: ['title'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}).not.toThrow();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw if collection meta is not a plain object', () => {
|
|
||||||
expect(() => {
|
|
||||||
validateConfig(merge({}, validConfig, { collections: [{ meta: [] }] }));
|
|
||||||
}).toThrowError("'collections[0].meta' must be object");
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw if collection meta is an empty object', () => {
|
|
||||||
expect(() => {
|
|
||||||
validateConfig(merge({}, validConfig, { collections: [{ meta: {} }] }));
|
|
||||||
}).toThrowError("'collections[0].meta' must NOT have fewer than 1 items");
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw if collection meta is an empty object', () => {
|
|
||||||
expect(() => {
|
|
||||||
validateConfig(merge({}, validConfig, { collections: [{ meta: { path: {} } }] }));
|
|
||||||
}).toThrowError("'collections[0].meta.path' must have required property 'label'");
|
|
||||||
expect(() => {
|
|
||||||
validateConfig(
|
|
||||||
merge({}, validConfig, { collections: [{ meta: { path: { label: 'Label' } } }] }),
|
|
||||||
);
|
|
||||||
}).toThrowError("'collections[0].meta.path' must have required property 'widget'");
|
|
||||||
expect(() => {
|
|
||||||
validateConfig(
|
|
||||||
merge({}, validConfig, {
|
|
||||||
collections: [{ meta: { path: { label: 'Label', widget: 'widget' } } }],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}).toThrowError("'collections[0].meta.path' must have required property 'index_file'");
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should allow collection meta to have a path configuration', () => {
|
|
||||||
expect(() => {
|
|
||||||
validateConfig(
|
|
||||||
merge({}, validConfig, {
|
|
||||||
collections: [
|
|
||||||
{ meta: { path: { label: 'Path', widget: 'string', index_file: 'index' } } },
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}).not.toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw if collection field pattern is not an array', () => {
|
|
||||||
expect(() => {
|
|
||||||
validateConfig(merge({}, validConfig, { collections: [{ fields: [{ pattern: '' }] }] }));
|
|
||||||
}).toThrowError("'collections[0].fields[0].pattern' must be array");
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw if collection field pattern is not an array of [string|regex, string]', () => {
|
|
||||||
expect(() => {
|
|
||||||
validateConfig(
|
|
||||||
merge({}, validConfig, { collections: [{ fields: [{ pattern: [1, ''] }] }] }),
|
|
||||||
);
|
|
||||||
}).toThrowError(
|
|
||||||
"'collections[0].fields[0].pattern[0]' must be string\n'collections[0].fields[0].pattern[0]' must be a regular expression",
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(() => {
|
|
||||||
validateConfig(
|
|
||||||
merge({}, validConfig, { collections: [{ fields: [{ pattern: ['', 1] }] }] }),
|
|
||||||
);
|
|
||||||
}).toThrowError("'collections[0].fields[0].pattern[1]' must be string");
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should allow collection field pattern to be an array of [string|regex, string]', () => {
|
|
||||||
expect(() => {
|
|
||||||
validateConfig(
|
|
||||||
merge({}, validConfig, {
|
|
||||||
collections: [{ fields: [{ pattern: ['pattern', 'error'] }] }],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}).not.toThrow();
|
|
||||||
|
|
||||||
expect(() => {
|
|
||||||
validateConfig(
|
|
||||||
merge({}, validConfig, {
|
|
||||||
collections: [{ fields: [{ pattern: [/pattern/, 'error'] }] }],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}).not.toThrow();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('i18n', () => {
|
|
||||||
it('should throw error when locale has invalid characters', () => {
|
|
||||||
expect(() => {
|
|
||||||
validateConfig(
|
|
||||||
merge({}, validConfig, {
|
|
||||||
i18n: {
|
|
||||||
structure: 'multiple_folders',
|
|
||||||
locales: ['en', 'tr.TR'],
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}).toThrowError(`'i18n.locales[1]' must match pattern "^[a-zA-Z-_]+$"`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw error when locale is less than 2 characters', () => {
|
|
||||||
expect(() => {
|
|
||||||
validateConfig(
|
|
||||||
merge({}, validConfig, {
|
|
||||||
i18n: {
|
|
||||||
structure: 'multiple_folders',
|
|
||||||
locales: ['en', 't'],
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}).toThrowError(`'i18n.locales[1]' must NOT have fewer than 2 characters`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw error when locale is more than 10 characters', () => {
|
|
||||||
expect(() => {
|
|
||||||
validateConfig(
|
|
||||||
merge({}, validConfig, {
|
|
||||||
i18n: {
|
|
||||||
structure: 'multiple_folders',
|
|
||||||
locales: ['en', 'a_very_long_locale'],
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}).toThrowError(`'i18n.locales[1]' must NOT have more than 10 characters`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should allow valid locales strings', () => {
|
|
||||||
expect(() => {
|
|
||||||
validateConfig(
|
|
||||||
merge({}, validConfig, {
|
|
||||||
i18n: {
|
|
||||||
structure: 'multiple_folders',
|
|
||||||
locales: ['en', 'tr-TR', 'zh_CHS'],
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}).not.toThrow();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,429 +0,0 @@
|
|||||||
import {
|
|
||||||
FrontmatterInfer,
|
|
||||||
frontmatterJSON,
|
|
||||||
frontmatterTOML,
|
|
||||||
frontmatterYAML,
|
|
||||||
} from '../frontmatter';
|
|
||||||
|
|
||||||
describe('Frontmatter', () => {
|
|
||||||
describe('yaml', () => {
|
|
||||||
it('should parse YAML with --- delimiters', () => {
|
|
||||||
expect(
|
|
||||||
FrontmatterInfer.fromFile('---\ntitle: YAML\ndescription: Something longer\n---\nContent'),
|
|
||||||
).toEqual({
|
|
||||||
title: 'YAML',
|
|
||||||
description: 'Something longer',
|
|
||||||
body: 'Content',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should parse YAML with --- delimiters when it is explicitly set as the format without a custom delimiter', () => {
|
|
||||||
expect(
|
|
||||||
frontmatterYAML().fromFile('---\ntitle: YAML\ndescription: Something longer\n---\nContent'),
|
|
||||||
).toEqual({
|
|
||||||
title: 'YAML',
|
|
||||||
description: 'Something longer',
|
|
||||||
body: 'Content',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should parse YAML with custom delimiters when it is explicitly set as the format with a custom delimiter', () => {
|
|
||||||
expect(
|
|
||||||
frontmatterYAML('~~~').fromFile(
|
|
||||||
'~~~\ntitle: YAML\ndescription: Something longer\n~~~\nContent',
|
|
||||||
),
|
|
||||||
).toEqual({
|
|
||||||
title: 'YAML',
|
|
||||||
description: 'Something longer',
|
|
||||||
body: 'Content',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should parse YAML with custom delimiters when it is explicitly set as the format with different custom delimiters', () => {
|
|
||||||
expect(
|
|
||||||
frontmatterYAML(['~~~', '^^^']).fromFile(
|
|
||||||
'~~~\ntitle: YAML\ndescription: Something longer\n^^^\nContent',
|
|
||||||
),
|
|
||||||
).toEqual({
|
|
||||||
title: 'YAML',
|
|
||||||
description: 'Something longer',
|
|
||||||
body: 'Content',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should parse YAML with ---yaml delimiters', () => {
|
|
||||||
expect(
|
|
||||||
FrontmatterInfer.fromFile(
|
|
||||||
'---yaml\ntitle: YAML\ndescription: Something longer\n---\nContent',
|
|
||||||
),
|
|
||||||
).toEqual({
|
|
||||||
title: 'YAML',
|
|
||||||
description: 'Something longer',
|
|
||||||
body: 'Content',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should overwrite any body param in the front matter', () => {
|
|
||||||
expect(
|
|
||||||
FrontmatterInfer.fromFile('---\ntitle: The Title\nbody: Something longer\n---\nContent'),
|
|
||||||
).toEqual({
|
|
||||||
title: 'The Title',
|
|
||||||
body: 'Content',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should stringify YAML with --- delimiters', () => {
|
|
||||||
expect(
|
|
||||||
FrontmatterInfer.toFile({
|
|
||||||
body: 'Some content\nOn another line',
|
|
||||||
tags: ['front matter', 'yaml'],
|
|
||||||
title: 'YAML',
|
|
||||||
}),
|
|
||||||
).toEqual(
|
|
||||||
[
|
|
||||||
'---',
|
|
||||||
'tags:',
|
|
||||||
' - front matter',
|
|
||||||
' - yaml',
|
|
||||||
'title: YAML',
|
|
||||||
'---',
|
|
||||||
'Some content',
|
|
||||||
'On another line',
|
|
||||||
].join('\n'),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should stringify YAML with missing body', () => {
|
|
||||||
expect(FrontmatterInfer.toFile({ tags: ['front matter', 'yaml'], title: 'YAML' })).toEqual(
|
|
||||||
['---', 'tags:', ' - front matter', ' - yaml', 'title: YAML', '---', ''].join('\n'),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should stringify YAML with --- delimiters when it is explicitly set as the format without a custom delimiter', () => {
|
|
||||||
expect(
|
|
||||||
frontmatterYAML().toFile({
|
|
||||||
body: 'Some content\nOn another line',
|
|
||||||
tags: ['front matter', 'yaml'],
|
|
||||||
title: 'YAML',
|
|
||||||
}),
|
|
||||||
).toEqual(
|
|
||||||
[
|
|
||||||
'---',
|
|
||||||
'tags:',
|
|
||||||
' - front matter',
|
|
||||||
' - yaml',
|
|
||||||
'title: YAML',
|
|
||||||
'---',
|
|
||||||
'Some content',
|
|
||||||
'On another line',
|
|
||||||
].join('\n'),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should stringify YAML with --- delimiters when it is explicitly set as the format with a custom delimiter', () => {
|
|
||||||
expect(
|
|
||||||
frontmatterYAML('~~~').toFile({
|
|
||||||
body: 'Some content\nOn another line',
|
|
||||||
tags: ['front matter', 'yaml'],
|
|
||||||
title: 'YAML',
|
|
||||||
}),
|
|
||||||
).toEqual(
|
|
||||||
[
|
|
||||||
'~~~',
|
|
||||||
'tags:',
|
|
||||||
' - front matter',
|
|
||||||
' - yaml',
|
|
||||||
'title: YAML',
|
|
||||||
'~~~',
|
|
||||||
'Some content',
|
|
||||||
'On another line',
|
|
||||||
].join('\n'),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should stringify YAML with --- delimiters when it is explicitly set as the format with different custom delimiters', () => {
|
|
||||||
expect(
|
|
||||||
frontmatterYAML(['~~~', '^^^']).toFile({
|
|
||||||
body: 'Some content\nOn another line',
|
|
||||||
tags: ['front matter', 'yaml'],
|
|
||||||
title: 'YAML',
|
|
||||||
}),
|
|
||||||
).toEqual(
|
|
||||||
[
|
|
||||||
'~~~',
|
|
||||||
'tags:',
|
|
||||||
' - front matter',
|
|
||||||
' - yaml',
|
|
||||||
'title: YAML',
|
|
||||||
'^^^',
|
|
||||||
'Some content',
|
|
||||||
'On another line',
|
|
||||||
].join('\n'),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should trim last line break if added by grey-matter', () => {
|
|
||||||
expect(
|
|
||||||
frontmatterYAML().toFile({
|
|
||||||
body: 'noLineBreak',
|
|
||||||
}),
|
|
||||||
).toEqual('noLineBreak');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not trim last line break if not added by grey-matter', () => {
|
|
||||||
expect(
|
|
||||||
frontmatterYAML().toFile({
|
|
||||||
body: 'withLineBreak\n',
|
|
||||||
}),
|
|
||||||
).toEqual('withLineBreak\n');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should keep field types', () => {
|
|
||||||
const frontmatter = frontmatterYAML();
|
|
||||||
const file = frontmatter.toFile({
|
|
||||||
number: 1,
|
|
||||||
string: 'Hello World!',
|
|
||||||
date: new Date('2020-01-01'),
|
|
||||||
array: ['1', new Date('2020-01-01')],
|
|
||||||
body: 'Content',
|
|
||||||
});
|
|
||||||
expect(frontmatter.fromFile(file)).toEqual({
|
|
||||||
number: 1,
|
|
||||||
string: 'Hello World!',
|
|
||||||
date: new Date('2020-01-01'),
|
|
||||||
array: ['1', new Date('2020-01-01')],
|
|
||||||
body: 'Content',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('toml', () => {
|
|
||||||
it('should parse TOML with +++ delimiters', () => {
|
|
||||||
expect(
|
|
||||||
FrontmatterInfer.fromFile(
|
|
||||||
'+++\ntitle = "TOML"\ndescription = "Front matter"\n+++\nContent',
|
|
||||||
),
|
|
||||||
).toEqual({
|
|
||||||
title: 'TOML',
|
|
||||||
description: 'Front matter',
|
|
||||||
body: 'Content',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should parse TOML with 0.5 style dates', () => {
|
|
||||||
expect(
|
|
||||||
FrontmatterInfer.fromFile('+++\ntitle = "TOML"\ndate = 2018-12-24\n+++\nContent'),
|
|
||||||
).toEqual({
|
|
||||||
title: 'TOML',
|
|
||||||
date: new Date('2018-12-24T00:00:00.000Z'),
|
|
||||||
body: 'Content',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should parse TOML with +++ delimiters when it is explicitly set as the format without a custom delimiter', () => {
|
|
||||||
expect(
|
|
||||||
frontmatterTOML('~~~').fromFile(
|
|
||||||
'~~~\ntitle = "TOML"\ndescription = "Front matter"\n~~~\nContent',
|
|
||||||
),
|
|
||||||
).toEqual({
|
|
||||||
title: 'TOML',
|
|
||||||
description: 'Front matter',
|
|
||||||
body: 'Content',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should parse TOML with ---toml delimiters', () => {
|
|
||||||
expect(
|
|
||||||
FrontmatterInfer.fromFile(
|
|
||||||
'---toml\ntitle = "TOML"\ndescription = "Something longer"\n---\nContent',
|
|
||||||
),
|
|
||||||
).toEqual({
|
|
||||||
title: 'TOML',
|
|
||||||
description: 'Something longer',
|
|
||||||
body: 'Content',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should stringify TOML with +++ delimiters when it is explicitly set as the format without a custom delimiter', () => {
|
|
||||||
expect(
|
|
||||||
frontmatterTOML().toFile({
|
|
||||||
body: 'Some content\nOn another line',
|
|
||||||
tags: ['front matter', 'toml'],
|
|
||||||
title: 'TOML',
|
|
||||||
}),
|
|
||||||
).toEqual(
|
|
||||||
[
|
|
||||||
'+++',
|
|
||||||
'tags = ["front matter", "toml"]',
|
|
||||||
'title = "TOML"',
|
|
||||||
'+++',
|
|
||||||
'Some content',
|
|
||||||
'On another line',
|
|
||||||
].join('\n'),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should stringify TOML with +++ delimiters when it is explicitly set as the format with a custom delimiter', () => {
|
|
||||||
expect(
|
|
||||||
frontmatterTOML('~~~').toFile({
|
|
||||||
body: 'Some content\nOn another line',
|
|
||||||
tags: ['front matter', 'toml'],
|
|
||||||
title: 'TOML',
|
|
||||||
}),
|
|
||||||
).toEqual(
|
|
||||||
[
|
|
||||||
'~~~',
|
|
||||||
'tags = ["front matter", "toml"]',
|
|
||||||
'title = "TOML"',
|
|
||||||
'~~~',
|
|
||||||
'Some content',
|
|
||||||
'On another line',
|
|
||||||
].join('\n'),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should keep field types', () => {
|
|
||||||
const frontmatter = frontmatterTOML();
|
|
||||||
const file = frontmatter.toFile({
|
|
||||||
number: 1,
|
|
||||||
string: 'Hello World!',
|
|
||||||
date: new Date('2020-01-01'),
|
|
||||||
// in toml arrays must contain the same type
|
|
||||||
array: ['1', new Date('2020-01-01').toISOString()],
|
|
||||||
body: 'Content',
|
|
||||||
});
|
|
||||||
expect(frontmatter.fromFile(file)).toEqual({
|
|
||||||
number: 1,
|
|
||||||
string: 'Hello World!',
|
|
||||||
date: new Date('2020-01-01'),
|
|
||||||
array: ['1', new Date('2020-01-01').toISOString()],
|
|
||||||
body: 'Content',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('json', () => {
|
|
||||||
it('should parse JSON with { } delimiters', () => {
|
|
||||||
expect(
|
|
||||||
FrontmatterInfer.fromFile(
|
|
||||||
'{\n"title": "The Title",\n"description": "Something longer"\n}\nContent',
|
|
||||||
),
|
|
||||||
).toEqual({
|
|
||||||
title: 'The Title',
|
|
||||||
description: 'Something longer',
|
|
||||||
body: 'Content',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should parse JSON with { } delimiters when it is explicitly set as the format without a custom delimiter', () => {
|
|
||||||
expect(
|
|
||||||
frontmatterJSON().fromFile(
|
|
||||||
'{\n"title": "The Title",\n"description": "Something longer"\n}\nContent',
|
|
||||||
),
|
|
||||||
).toEqual({
|
|
||||||
title: 'The Title',
|
|
||||||
description: 'Something longer',
|
|
||||||
body: 'Content',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should parse JSON with { } delimiters when it is explicitly set as the format with a custom delimiter', () => {
|
|
||||||
expect(
|
|
||||||
frontmatterJSON('~~~').fromFile(
|
|
||||||
'~~~\n"title": "The Title",\n"description": "Something longer"\n~~~\nContent',
|
|
||||||
),
|
|
||||||
).toEqual({
|
|
||||||
title: 'The Title',
|
|
||||||
description: 'Something longer',
|
|
||||||
body: 'Content',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should parse JSON with ---json delimiters', () => {
|
|
||||||
expect(
|
|
||||||
FrontmatterInfer.fromFile(
|
|
||||||
'---json\n{\n"title": "The Title",\n"description": "Something longer"\n}\n---\nContent',
|
|
||||||
),
|
|
||||||
).toEqual({
|
|
||||||
title: 'The Title',
|
|
||||||
description: 'Something longer',
|
|
||||||
body: 'Content',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should parse JSON with { } delimiters ending with a nested object', () => {
|
|
||||||
expect(
|
|
||||||
FrontmatterInfer.fromFile(
|
|
||||||
'{\n "title": "The Title",\n "nested": {\n "inside": "Inside prop"\n }\n}\nContent',
|
|
||||||
),
|
|
||||||
).toEqual({
|
|
||||||
title: 'The Title',
|
|
||||||
nested: { inside: 'Inside prop' },
|
|
||||||
body: 'Content',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should stringify JSON with { } delimiters when it is explicitly set as the format without a custom delimiter', () => {
|
|
||||||
expect(
|
|
||||||
frontmatterJSON().toFile({
|
|
||||||
body: 'Some content\nOn another line',
|
|
||||||
tags: ['front matter', 'json'],
|
|
||||||
title: 'JSON',
|
|
||||||
}),
|
|
||||||
).toEqual(
|
|
||||||
[
|
|
||||||
'{',
|
|
||||||
'"tags": [',
|
|
||||||
' "front matter",',
|
|
||||||
' "json"',
|
|
||||||
' ],',
|
|
||||||
' "title": "JSON"',
|
|
||||||
'}',
|
|
||||||
'Some content',
|
|
||||||
'On another line',
|
|
||||||
].join('\n'),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should stringify JSON with { } delimiters when it is explicitly set as the format with a custom delimiter', () => {
|
|
||||||
expect(
|
|
||||||
frontmatterJSON('~~~').toFile({
|
|
||||||
body: 'Some content\nOn another line',
|
|
||||||
tags: ['front matter', 'json'],
|
|
||||||
title: 'JSON',
|
|
||||||
}),
|
|
||||||
).toEqual(
|
|
||||||
[
|
|
||||||
'~~~',
|
|
||||||
'"tags": [',
|
|
||||||
' "front matter",',
|
|
||||||
' "json"',
|
|
||||||
' ],',
|
|
||||||
' "title": "JSON"',
|
|
||||||
'~~~',
|
|
||||||
'Some content',
|
|
||||||
'On another line',
|
|
||||||
].join('\n'),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should keep field types', () => {
|
|
||||||
const frontmatter = frontmatterJSON();
|
|
||||||
const file = frontmatter.toFile({
|
|
||||||
number: 1,
|
|
||||||
string: 'Hello World!',
|
|
||||||
// no way to represent date in JSON
|
|
||||||
date: new Date('2020-01-01').toISOString(),
|
|
||||||
array: ['1', new Date('2020-01-01').toISOString()],
|
|
||||||
body: 'Content',
|
|
||||||
});
|
|
||||||
expect(frontmatter.fromFile(file)).toEqual({
|
|
||||||
number: 1,
|
|
||||||
string: 'Hello World!',
|
|
||||||
date: new Date('2020-01-01').toISOString(),
|
|
||||||
array: ['1', new Date('2020-01-01').toISOString()],
|
|
||||||
body: 'Content',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,9 +0,0 @@
|
|||||||
import tomlFormatter from '../toml';
|
|
||||||
|
|
||||||
describe('tomlFormatter', () => {
|
|
||||||
it('should output TOML integer values without decimals', () => {
|
|
||||||
expect(tomlFormatter.toFile({ testFloat: 123.456, testInteger: 789, title: 'TOML' })).toEqual(
|
|
||||||
['testFloat = 123.456', 'testInteger = 789', 'title = "TOML"'].join('\n'),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,162 +0,0 @@
|
|||||||
import { stripIndent } from 'common-tags';
|
|
||||||
|
|
||||||
import yaml from '../yaml';
|
|
||||||
|
|
||||||
describe('yaml', () => {
|
|
||||||
describe('fromFile', () => {
|
|
||||||
test('loads valid yaml', () => {
|
|
||||||
expect(yaml.fromFile('[]')).toEqual([]);
|
|
||||||
|
|
||||||
const result = yaml.fromFile(stripIndent`
|
|
||||||
date: 2020-04-02T16:08:03.327Z
|
|
||||||
dateString: 2020-04-02
|
|
||||||
boolean: true
|
|
||||||
number: 1
|
|
||||||
`);
|
|
||||||
expect(result).toEqual({
|
|
||||||
date: new Date('2020-04-02T16:08:03.327Z'),
|
|
||||||
dateString: '2020-04-02',
|
|
||||||
boolean: true,
|
|
||||||
number: 1,
|
|
||||||
});
|
|
||||||
expect(yaml.fromFile('# Comment a\na: a\nb:\n # Comment c\n c:\n d: d\n')).toEqual({
|
|
||||||
a: 'a',
|
|
||||||
b: { c: { d: 'd' } },
|
|
||||||
});
|
|
||||||
expect(
|
|
||||||
yaml.fromFile(stripIndent`
|
|
||||||
# template comment
|
|
||||||
template: post
|
|
||||||
# title comment
|
|
||||||
title: title
|
|
||||||
# image comment
|
|
||||||
image: /media/netlify.png
|
|
||||||
# date comment
|
|
||||||
date: 2020-04-02T13:27:48.617Z
|
|
||||||
# object comment
|
|
||||||
object:
|
|
||||||
# object_title comment
|
|
||||||
object_title: object_title
|
|
||||||
# object_list comment
|
|
||||||
object_list:
|
|
||||||
- object_list_item_1: "1"
|
|
||||||
object_list_item_2: "2"
|
|
||||||
# list comment
|
|
||||||
list:
|
|
||||||
- "1"
|
|
||||||
`),
|
|
||||||
).toEqual({
|
|
||||||
list: ['1'],
|
|
||||||
object: {
|
|
||||||
object_title: 'object_title',
|
|
||||||
object_list: [{ object_list_item_1: '1', object_list_item_2: '2' }],
|
|
||||||
},
|
|
||||||
date: new Date('2020-04-02T13:27:48.617Z'),
|
|
||||||
image: '/media/netlify.png',
|
|
||||||
title: 'title',
|
|
||||||
template: 'post',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
test('does not fail on closing separator', () => {
|
|
||||||
expect(yaml.fromFile('---\n[]\n---')).toEqual([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('parses single quoted string as string', () => {
|
|
||||||
expect(yaml.fromFile('name: y')).toEqual({ name: 'y' });
|
|
||||||
});
|
|
||||||
|
|
||||||
test('parses ISO date string as date', () => {
|
|
||||||
expect(yaml.fromFile('date: 2020-04-02T16:08:03.327Z')).toEqual({
|
|
||||||
date: new Date('2020-04-02T16:08:03.327Z'),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('parses partial date string as string', () => {
|
|
||||||
expect(yaml.fromFile('date: 2020-06-12')).toEqual({
|
|
||||||
date: '2020-06-12',
|
|
||||||
});
|
|
||||||
expect(yaml.fromFile('date: 12-06-2012')).toEqual({
|
|
||||||
date: '12-06-2012',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('parses partial time value as string', () => {
|
|
||||||
expect(yaml.fromFile('time: 10:05')).toEqual({
|
|
||||||
time: '10:05',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe('toFile', () => {
|
|
||||||
test('outputs valid yaml', () => {
|
|
||||||
expect(yaml.toFile([])).toEqual('[]\n');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should sort keys', () => {
|
|
||||||
expect(yaml.toFile({ a: 'a', b: 'b', c: 'c', d: 'd' })).toEqual('a: a\nb: b\nc: c\nd: d\n');
|
|
||||||
expect(yaml.toFile({ a: 'a', b: 'b', c: 'c', d: 'd' }, ['d', 'b', 'a', 'c'])).toEqual(
|
|
||||||
'd: d\nb: b\na: a\nc: c\n',
|
|
||||||
);
|
|
||||||
expect(yaml.toFile({ a: 'a', b: 'b', c: 'c', d: 'd' }, ['d', 'b', 'c'])).toEqual(
|
|
||||||
'a: a\nd: d\nb: b\nc: c\n',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should add comments', () => {
|
|
||||||
expect(
|
|
||||||
yaml.toFile({ a: 'a', b: { c: { d: 'd' } } }, [], { a: 'Comment a', 'b.c': 'Comment c' }),
|
|
||||||
).toEqual('# Comment a\na: a\nb:\n # Comment c\n c:\n d: d\n');
|
|
||||||
|
|
||||||
const expected = `# template comment
|
|
||||||
template: post
|
|
||||||
# title comment
|
|
||||||
title: title
|
|
||||||
# image comment
|
|
||||||
image: /media/netlify.png
|
|
||||||
# date comment
|
|
||||||
date: 2020-04-02T13:27:48.617Z
|
|
||||||
# object comment
|
|
||||||
object:
|
|
||||||
# object_title comment
|
|
||||||
object_title: object_title
|
|
||||||
# object_list comment
|
|
||||||
object_list:
|
|
||||||
- object_list_item_1: "1"
|
|
||||||
object_list_item_2: "2"
|
|
||||||
# list comment
|
|
||||||
list:
|
|
||||||
- "1"
|
|
||||||
`;
|
|
||||||
|
|
||||||
const result = yaml.toFile(
|
|
||||||
{
|
|
||||||
list: ['1'],
|
|
||||||
object: {
|
|
||||||
object_title: 'object_title',
|
|
||||||
object_list: [{ object_list_item_1: '1', object_list_item_2: '2' }],
|
|
||||||
},
|
|
||||||
date: new Date('2020-04-02T13:27:48.617Z'),
|
|
||||||
image: '/media/netlify.png',
|
|
||||||
title: 'title',
|
|
||||||
template: 'post',
|
|
||||||
},
|
|
||||||
['template', 'title', 'image', 'date', 'object', 'list'],
|
|
||||||
{
|
|
||||||
list: 'list comment',
|
|
||||||
object: 'object comment',
|
|
||||||
'object.object_title': 'object_title comment',
|
|
||||||
'object.object_list': 'object_list comment',
|
|
||||||
date: 'date comment',
|
|
||||||
image: 'image comment',
|
|
||||||
title: 'title comment',
|
|
||||||
template: 'template comment',
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(result).toEqual(expected);
|
|
||||||
|
|
||||||
expect(yaml.toFile({ a: 'a' }, [], { a: 'line 1\\nline 2' })).toEqual(
|
|
||||||
'# line 1\n# line 2\na: a\n',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,38 +0,0 @@
|
|||||||
import { authenticating, authenticate, authError, logout } from '../../actions/auth';
|
|
||||||
import auth, { defaultState } from '../auth';
|
|
||||||
|
|
||||||
describe('auth', () => {
|
|
||||||
it('should handle an empty state', () => {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore auth reducer doesn't accept empty action
|
|
||||||
expect(auth(undefined, {})).toEqual(defaultState);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle an authentication request', () => {
|
|
||||||
expect(auth(undefined, authenticating())).toEqual({
|
|
||||||
...defaultState,
|
|
||||||
isFetching: true,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle authentication', () => {
|
|
||||||
const user = { name: 'joe', token: 'token' };
|
|
||||||
expect(auth(undefined, authenticate(user))).toEqual({
|
|
||||||
...defaultState,
|
|
||||||
user,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle an authentication error', () => {
|
|
||||||
expect(auth(undefined, authError(new Error('Bad credentials')))).toEqual({
|
|
||||||
...defaultState,
|
|
||||||
error: 'Error: Bad credentials',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle logout', () => {
|
|
||||||
const user = { name: 'joe', token: 'token' };
|
|
||||||
const newState = auth({ ...defaultState, user }, logout());
|
|
||||||
expect(newState.user).toBeUndefined();
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,571 +0,0 @@
|
|||||||
import { fromJS, Map } from 'immutable';
|
|
||||||
|
|
||||||
import { configLoaded } from '../../actions/config';
|
|
||||||
import collections, {
|
|
||||||
selectAllowDeletion,
|
|
||||||
selectEntryPath,
|
|
||||||
selectEntrySlug,
|
|
||||||
selectFieldsWithMediaFolders,
|
|
||||||
selectMediaFolders,
|
|
||||||
selectEntryCollectionTitle,
|
|
||||||
getFieldsNames,
|
|
||||||
selectField,
|
|
||||||
updateFieldByKey,
|
|
||||||
} from '../collections';
|
|
||||||
import { FILES, FOLDER } from '../../constants/collectionTypes';
|
|
||||||
|
|
||||||
describe('collections', () => {
|
|
||||||
it('should handle an empty state', () => {
|
|
||||||
expect(collections(undefined, {})).toEqual(Map());
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should load the collections from the config', () => {
|
|
||||||
expect(
|
|
||||||
collections(
|
|
||||||
undefined,
|
|
||||||
configLoaded({
|
|
||||||
collections: [
|
|
||||||
{
|
|
||||||
name: 'posts',
|
|
||||||
folder: '_posts',
|
|
||||||
fields: [{ name: 'title', widget: 'string' }],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
).toJS(),
|
|
||||||
).toEqual({
|
|
||||||
posts: {
|
|
||||||
name: 'posts',
|
|
||||||
folder: '_posts',
|
|
||||||
fields: [{ name: 'title', widget: 'string' }],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should maintain config collections order', () => {
|
|
||||||
const collectionsData = new Array(1000).fill(0).map((_, index) => ({
|
|
||||||
name: `collection_${index}`,
|
|
||||||
folder: `collection_${index}`,
|
|
||||||
fields: [{ name: 'title', widget: 'string' }],
|
|
||||||
}));
|
|
||||||
|
|
||||||
const newState = collections(
|
|
||||||
undefined,
|
|
||||||
configLoaded({
|
|
||||||
collections: collectionsData,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
const keyArray = newState.keySeq().toArray();
|
|
||||||
expect(keyArray).toEqual(collectionsData.map(({ name }) => name));
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('selectAllowDeletions', () => {
|
|
||||||
it('should not allow deletions for file collections', () => {
|
|
||||||
expect(
|
|
||||||
selectAllowDeletion(
|
|
||||||
fromJS({
|
|
||||||
name: 'pages',
|
|
||||||
type: FILES,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('selectEntryPath', () => {
|
|
||||||
it('should return path', () => {
|
|
||||||
expect(
|
|
||||||
selectEntryPath(
|
|
||||||
fromJS({
|
|
||||||
type: FOLDER,
|
|
||||||
folder: 'posts',
|
|
||||||
}),
|
|
||||||
'dir1/dir2/slug',
|
|
||||||
),
|
|
||||||
).toBe('posts/dir1/dir2/slug.md');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('selectEntrySlug', () => {
|
|
||||||
it('should return slug', () => {
|
|
||||||
expect(
|
|
||||||
selectEntrySlug(
|
|
||||||
fromJS({
|
|
||||||
type: FOLDER,
|
|
||||||
folder: 'posts',
|
|
||||||
}),
|
|
||||||
'posts/dir1/dir2/slug.md',
|
|
||||||
),
|
|
||||||
).toBe('dir1/dir2/slug');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('selectFieldsMediaFolders', () => {
|
|
||||||
it('should return empty array for invalid collection', () => {
|
|
||||||
expect(selectFieldsWithMediaFolders(fromJS({}))).toEqual([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return configs for folder collection', () => {
|
|
||||||
expect(
|
|
||||||
selectFieldsWithMediaFolders(
|
|
||||||
fromJS({
|
|
||||||
folder: 'posts',
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: 'image',
|
|
||||||
media_folder: 'image_media_folder',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'body',
|
|
||||||
media_folder: 'body_media_folder',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'list_1',
|
|
||||||
field: {
|
|
||||||
name: 'list_1_item',
|
|
||||||
media_folder: 'list_1_item_media_folder',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'list_2',
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: 'list_2_item',
|
|
||||||
media_folder: 'list_2_item_media_folder',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'list_3',
|
|
||||||
types: [
|
|
||||||
{
|
|
||||||
name: 'list_3_type',
|
|
||||||
media_folder: 'list_3_type_media_folder',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
).toEqual([
|
|
||||||
fromJS({
|
|
||||||
name: 'image',
|
|
||||||
media_folder: 'image_media_folder',
|
|
||||||
}),
|
|
||||||
fromJS({ name: 'body', media_folder: 'body_media_folder' }),
|
|
||||||
fromJS({ name: 'list_1_item', media_folder: 'list_1_item_media_folder' }),
|
|
||||||
fromJS({
|
|
||||||
name: 'list_2_item',
|
|
||||||
media_folder: 'list_2_item_media_folder',
|
|
||||||
}),
|
|
||||||
fromJS({
|
|
||||||
name: 'list_3_type',
|
|
||||||
media_folder: 'list_3_type_media_folder',
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return configs for files collection', () => {
|
|
||||||
expect(
|
|
||||||
selectFieldsWithMediaFolders(
|
|
||||||
fromJS({
|
|
||||||
files: [
|
|
||||||
{
|
|
||||||
name: 'file1',
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: 'image',
|
|
||||||
media_folder: 'image_media_folder',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'file2',
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: 'body',
|
|
||||||
media_folder: 'body_media_folder',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'file3',
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: 'list_1',
|
|
||||||
field: {
|
|
||||||
name: 'list_1_item',
|
|
||||||
media_folder: 'list_1_item_media_folder',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'file4',
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: 'list_2',
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: 'list_2_item',
|
|
||||||
media_folder: 'list_2_item_media_folder',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'list_3',
|
|
||||||
types: [
|
|
||||||
{
|
|
||||||
name: 'list_3_type',
|
|
||||||
media_folder: 'list_3_type_media_folder',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
'file4',
|
|
||||||
),
|
|
||||||
).toEqual([
|
|
||||||
fromJS({
|
|
||||||
name: 'list_2_item',
|
|
||||||
media_folder: 'list_2_item_media_folder',
|
|
||||||
}),
|
|
||||||
fromJS({
|
|
||||||
name: 'list_3_type',
|
|
||||||
media_folder: 'list_3_type_media_folder',
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('selectMediaFolders', () => {
|
|
||||||
const slug = {
|
|
||||||
encoding: 'unicode',
|
|
||||||
clean_accents: false,
|
|
||||||
sanitize_replacement: '-',
|
|
||||||
};
|
|
||||||
|
|
||||||
const config = { slug, media_folder: '/static/img' };
|
|
||||||
it('should return fields and collection folders', () => {
|
|
||||||
expect(
|
|
||||||
selectMediaFolders(
|
|
||||||
config,
|
|
||||||
fromJS({
|
|
||||||
folder: 'posts',
|
|
||||||
media_folder: '{{media_folder}}/general/',
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: 'image',
|
|
||||||
media_folder: '{{media_folder}}/customers/',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'list',
|
|
||||||
types: [{ name: 'widget', media_folder: '{{media_folder}}/widgets' }],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
fromJS({ slug: 'name', path: 'src/post/post1.md', data: {} }),
|
|
||||||
),
|
|
||||||
).toEqual([
|
|
||||||
'static/img/general',
|
|
||||||
'static/img/general/customers',
|
|
||||||
'static/img/general/widgets',
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return fields, file and collection folders', () => {
|
|
||||||
expect(
|
|
||||||
selectMediaFolders(
|
|
||||||
config,
|
|
||||||
fromJS({
|
|
||||||
media_folder: '{{media_folder}}/general/',
|
|
||||||
files: [
|
|
||||||
{
|
|
||||||
name: 'name',
|
|
||||||
file: 'src/post/post1.md',
|
|
||||||
media_folder: '{{media_folder}}/customers/',
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: 'image',
|
|
||||||
media_folder: '{{media_folder}}/logos/',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'list',
|
|
||||||
types: [{ name: 'widget', media_folder: '{{media_folder}}/widgets' }],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
fromJS({ slug: 'name', path: 'src/post/post1.md', data: {} }),
|
|
||||||
),
|
|
||||||
).toEqual([
|
|
||||||
'static/img/general',
|
|
||||||
'static/img/general/customers',
|
|
||||||
'static/img/general/customers/logos',
|
|
||||||
'static/img/general/customers/widgets',
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getFieldsNames', () => {
|
|
||||||
it('should get flat fields names', () => {
|
|
||||||
const collection = fromJS({
|
|
||||||
fields: [{ name: 'en' }, { name: 'es' }],
|
|
||||||
});
|
|
||||||
expect(getFieldsNames(collection.get('fields').toArray())).toEqual(['en', 'es']);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should get nested fields names', () => {
|
|
||||||
const collection = fromJS({
|
|
||||||
fields: [
|
|
||||||
{ name: 'en', fields: [{ name: 'title' }, { name: 'body' }] },
|
|
||||||
{ name: 'es', fields: [{ name: 'title' }, { name: 'body' }] },
|
|
||||||
{ name: 'it', field: { name: 'title', fields: [{ name: 'subTitle' }] } },
|
|
||||||
{
|
|
||||||
name: 'fr',
|
|
||||||
fields: [{ name: 'title', widget: 'list', types: [{ name: 'variableType' }] }],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
expect(getFieldsNames(collection.get('fields').toArray())).toEqual([
|
|
||||||
'en',
|
|
||||||
'es',
|
|
||||||
'it',
|
|
||||||
'fr',
|
|
||||||
'en.title',
|
|
||||||
'en.body',
|
|
||||||
'es.title',
|
|
||||||
'es.body',
|
|
||||||
'it.title',
|
|
||||||
'it.title.subTitle',
|
|
||||||
'fr.title',
|
|
||||||
'fr.title.variableType',
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('selectField', () => {
|
|
||||||
it('should return top field by key', () => {
|
|
||||||
const collection = fromJS({
|
|
||||||
fields: [{ name: 'en' }, { name: 'es' }],
|
|
||||||
});
|
|
||||||
expect(selectField(collection, 'en')).toBe(collection.get('fields').get(0));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return nested field by key', () => {
|
|
||||||
const collection = fromJS({
|
|
||||||
fields: [
|
|
||||||
{ name: 'en', fields: [{ name: 'title' }, { name: 'body' }] },
|
|
||||||
{ name: 'es', fields: [{ name: 'title' }, { name: 'body' }] },
|
|
||||||
{ name: 'it', field: { name: 'title', fields: [{ name: 'subTitle' }] } },
|
|
||||||
{
|
|
||||||
name: 'fr',
|
|
||||||
fields: [{ name: 'title', widget: 'list', types: [{ name: 'variableType' }] }],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(selectField(collection, 'en.title')).toBe(
|
|
||||||
collection.get('fields').get(0).get('fields').get(0),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(selectField(collection, 'it.title.subTitle')).toBe(
|
|
||||||
collection.get('fields').get(2).get('field').get('fields').get(0),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(selectField(collection, 'fr.title.variableType')).toBe(
|
|
||||||
collection.get('fields').get(3).get('fields').get(0).get('types').get(0),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('selectEntryCollectionTitle', () => {
|
|
||||||
const entry = fromJS({
|
|
||||||
data: { title: 'entry title', otherField: 'other field', emptyLinkTitle: '' },
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return the entry title if set', () => {
|
|
||||||
const collection = fromJS({
|
|
||||||
fields: [{ name: 'title' }, { name: 'otherField' }],
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(selectEntryCollectionTitle(collection, entry)).toEqual('entry title');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return some other inferreable title if set', () => {
|
|
||||||
const headlineEntry = fromJS({
|
|
||||||
data: { headline: 'entry headline', otherField: 'other field' },
|
|
||||||
});
|
|
||||||
const collection = fromJS({
|
|
||||||
fields: [{ name: 'headline' }, { name: 'otherField' }],
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(selectEntryCollectionTitle(collection, headlineEntry)).toEqual('entry headline');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return the identifier_field content if defined in collection', () => {
|
|
||||||
const collection = fromJS({
|
|
||||||
identifier_field: 'otherField',
|
|
||||||
fields: [{ name: 'title' }, { name: 'otherField' }],
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(selectEntryCollectionTitle(collection, entry)).toEqual('other field');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return the entry title if identifier_field content is not defined in collection', () => {
|
|
||||||
const collection = fromJS({
|
|
||||||
identifier_field: 'missingLinkTitle',
|
|
||||||
fields: [{ name: 'title' }, { name: 'otherField' }],
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(selectEntryCollectionTitle(collection, entry)).toEqual('entry title');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return the entry title if identifier_field content is empty', () => {
|
|
||||||
const collection = fromJS({
|
|
||||||
identifier_field: 'emptyLinkTitle',
|
|
||||||
fields: [{ name: 'title' }, { name: 'otherField' }, { name: 'emptyLinkTitle' }],
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(selectEntryCollectionTitle(collection, entry)).toEqual('entry title');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return the entry label of a file collection', () => {
|
|
||||||
const labelEntry = fromJS({
|
|
||||||
slug: 'entry-name',
|
|
||||||
data: { title: 'entry title', otherField: 'other field' },
|
|
||||||
});
|
|
||||||
const collection = fromJS({
|
|
||||||
type: FILES,
|
|
||||||
files: [
|
|
||||||
{
|
|
||||||
name: 'entry-name',
|
|
||||||
label: 'entry label',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(selectEntryCollectionTitle(collection, labelEntry)).toEqual('entry label');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return a formatted summary before everything else', () => {
|
|
||||||
const collection = fromJS({
|
|
||||||
summary: '{{title}} -- {{otherField}}',
|
|
||||||
identifier_field: 'otherField',
|
|
||||||
fields: [{ name: 'title' }, { name: 'otherField' }],
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(selectEntryCollectionTitle(collection, entry)).toEqual('entry title -- other field');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('updateFieldByKey', () => {
|
|
||||||
it('should update field by key', () => {
|
|
||||||
const collection = fromJS({
|
|
||||||
fields: [
|
|
||||||
{ name: 'title' },
|
|
||||||
{ name: 'image' },
|
|
||||||
{
|
|
||||||
name: 'object',
|
|
||||||
fields: [{ name: 'title' }, { name: 'gallery', fields: [{ name: 'image' }] }],
|
|
||||||
},
|
|
||||||
{ name: 'list', field: { name: 'image' } },
|
|
||||||
{ name: 'body' },
|
|
||||||
{ name: 'widgetList', types: [{ name: 'widget' }] },
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
function updater(field) {
|
|
||||||
return field.set('default', 'default');
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(updateFieldByKey(collection, 'non-existent', updater)).toBe(collection);
|
|
||||||
expect(updateFieldByKey(collection, 'title', updater)).toEqual(
|
|
||||||
fromJS({
|
|
||||||
fields: [
|
|
||||||
{ name: 'title', default: 'default' },
|
|
||||||
{ name: 'image' },
|
|
||||||
{
|
|
||||||
name: 'object',
|
|
||||||
fields: [{ name: 'title' }, { name: 'gallery', fields: [{ name: 'image' }] }],
|
|
||||||
},
|
|
||||||
{ name: 'list', field: { name: 'image' } },
|
|
||||||
{ name: 'body' },
|
|
||||||
{ name: 'widgetList', types: [{ name: 'widget' }] },
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
expect(updateFieldByKey(collection, 'object.title', updater)).toEqual(
|
|
||||||
fromJS({
|
|
||||||
fields: [
|
|
||||||
{ name: 'title' },
|
|
||||||
{ name: 'image' },
|
|
||||||
{
|
|
||||||
name: 'object',
|
|
||||||
fields: [
|
|
||||||
{ name: 'title', default: 'default' },
|
|
||||||
{ name: 'gallery', fields: [{ name: 'image' }] },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{ name: 'list', field: { name: 'image' } },
|
|
||||||
{ name: 'body' },
|
|
||||||
{ name: 'widgetList', types: [{ name: 'widget' }] },
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(updateFieldByKey(collection, 'object.gallery.image', updater)).toEqual(
|
|
||||||
fromJS({
|
|
||||||
fields: [
|
|
||||||
{ name: 'title' },
|
|
||||||
{ name: 'image' },
|
|
||||||
{
|
|
||||||
name: 'object',
|
|
||||||
fields: [
|
|
||||||
{ name: 'title' },
|
|
||||||
{ name: 'gallery', fields: [{ name: 'image', default: 'default' }] },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{ name: 'list', field: { name: 'image' } },
|
|
||||||
{ name: 'body' },
|
|
||||||
{ name: 'widgetList', types: [{ name: 'widget' }] },
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
expect(updateFieldByKey(collection, 'list.image', updater)).toEqual(
|
|
||||||
fromJS({
|
|
||||||
fields: [
|
|
||||||
{ name: 'title' },
|
|
||||||
{ name: 'image' },
|
|
||||||
{
|
|
||||||
name: 'object',
|
|
||||||
fields: [{ name: 'title' }, { name: 'gallery', fields: [{ name: 'image' }] }],
|
|
||||||
},
|
|
||||||
{ name: 'list', field: { name: 'image', default: 'default' } },
|
|
||||||
{ name: 'body' },
|
|
||||||
{ name: 'widgetList', types: [{ name: 'widget' }] },
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(updateFieldByKey(collection, 'widgetList.widget', updater)).toEqual(
|
|
||||||
fromJS({
|
|
||||||
fields: [
|
|
||||||
{ name: 'title' },
|
|
||||||
{ name: 'image' },
|
|
||||||
{
|
|
||||||
name: 'object',
|
|
||||||
fields: [{ name: 'title' }, { name: 'gallery', fields: [{ name: 'image' }] }],
|
|
||||||
},
|
|
||||||
{ name: 'list', field: { name: 'image' } },
|
|
||||||
{ name: 'body' },
|
|
||||||
{ name: 'widgetList', types: [{ name: 'widget', default: 'default' }] },
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,38 +0,0 @@
|
|||||||
import { configLoaded, configLoading, configFailed } from '../../actions/config';
|
|
||||||
import config, { selectLocale } from '../config';
|
|
||||||
|
|
||||||
describe('config', () => {
|
|
||||||
it('should handle an empty state', () => {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// @ts-ignore config reducer doesn't accept empty action
|
|
||||||
expect(config(undefined, {})).toEqual({ isFetching: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle an update', () => {
|
|
||||||
expect(
|
|
||||||
config({ isFetching: true }, configLoaded({ locale: 'fr', backend: { name: 'proxy' } })),
|
|
||||||
).toEqual({
|
|
||||||
locale: 'fr',
|
|
||||||
backend: { name: 'proxy' },
|
|
||||||
isFetching: false,
|
|
||||||
error: undefined,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should mark the config as loading', () => {
|
|
||||||
expect(config({ isFetching: false }, configLoading())).toEqual({ isFetching: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle an error', () => {
|
|
||||||
expect(
|
|
||||||
config({ isFetching: true }, configFailed(new Error('Config could not be loaded'))),
|
|
||||||
).toEqual({
|
|
||||||
error: 'Error: Config could not be loaded',
|
|
||||||
isFetching: false,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should default to "en" locale', () => {
|
|
||||||
expect(selectLocale({})).toEqual('en');
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,694 +0,0 @@
|
|||||||
import { OrderedMap, fromJS } from 'immutable';
|
|
||||||
|
|
||||||
import * as actions from '../../actions/entries';
|
|
||||||
import reducer, {
|
|
||||||
selectMediaFolder,
|
|
||||||
selectMediaFilePath,
|
|
||||||
selectMediaFilePublicPath,
|
|
||||||
selectEntries,
|
|
||||||
} from '../entries';
|
|
||||||
|
|
||||||
const initialState = OrderedMap({
|
|
||||||
posts: fromJS({ name: 'posts' }),
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('entries', () => {
|
|
||||||
describe('reducer', () => {
|
|
||||||
it('should mark entries as fetching', () => {
|
|
||||||
expect(reducer(initialState, actions.entriesLoading(fromJS({ name: 'posts' })))).toEqual(
|
|
||||||
OrderedMap(
|
|
||||||
fromJS({
|
|
||||||
posts: { name: 'posts' },
|
|
||||||
pages: {
|
|
||||||
posts: { isFetching: true },
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle loaded entries', () => {
|
|
||||||
const entries = [
|
|
||||||
{ slug: 'a', path: '' },
|
|
||||||
{ slug: 'b', title: 'B' },
|
|
||||||
];
|
|
||||||
expect(
|
|
||||||
reducer(initialState, actions.entriesLoaded(fromJS({ name: 'posts' }), entries, 0)),
|
|
||||||
).toEqual(
|
|
||||||
OrderedMap(
|
|
||||||
fromJS({
|
|
||||||
posts: { name: 'posts' },
|
|
||||||
entities: {
|
|
||||||
'posts.a': { slug: 'a', path: '', isFetching: false },
|
|
||||||
'posts.b': { slug: 'b', title: 'B', isFetching: false },
|
|
||||||
},
|
|
||||||
pages: {
|
|
||||||
posts: {
|
|
||||||
page: 0,
|
|
||||||
ids: ['a', 'b'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle loaded entry', () => {
|
|
||||||
const entry = { slug: 'a', path: '' };
|
|
||||||
expect(reducer(initialState, actions.entryLoaded(fromJS({ name: 'posts' }), entry))).toEqual(
|
|
||||||
OrderedMap(
|
|
||||||
fromJS({
|
|
||||||
posts: { name: 'posts' },
|
|
||||||
entities: {
|
|
||||||
'posts.a': { slug: 'a', path: '' },
|
|
||||||
},
|
|
||||||
pages: {
|
|
||||||
posts: {
|
|
||||||
ids: ['a'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('selectMediaFolder', () => {
|
|
||||||
it("should return global media folder when collection doesn't specify media_folder", () => {
|
|
||||||
expect(
|
|
||||||
selectMediaFolder(
|
|
||||||
{ media_folder: 'static/media' },
|
|
||||||
fromJS({ name: 'posts' }),
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
),
|
|
||||||
).toEqual('static/media');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return draft media folder when collection specifies media_folder and entry is undefined', () => {
|
|
||||||
expect(
|
|
||||||
selectMediaFolder(
|
|
||||||
{ media_folder: 'static/media' },
|
|
||||||
fromJS({ name: 'posts', folder: 'posts', media_folder: '' }),
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
),
|
|
||||||
).toEqual('posts/DRAFT_MEDIA_FILES');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return relative media folder when collection specifies media_folder and entry path is not null', () => {
|
|
||||||
expect(
|
|
||||||
selectMediaFolder(
|
|
||||||
{ media_folder: 'static/media' },
|
|
||||||
fromJS({ name: 'posts', folder: 'posts', media_folder: '' }),
|
|
||||||
fromJS({ path: 'posts/title/index.md' }),
|
|
||||||
undefined,
|
|
||||||
),
|
|
||||||
).toEqual('posts/title');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should resolve collection relative media folder', () => {
|
|
||||||
expect(
|
|
||||||
selectMediaFolder(
|
|
||||||
fromJS({ media_folder: 'static/media' }),
|
|
||||||
fromJS({ name: 'posts', folder: 'posts', media_folder: '../' }),
|
|
||||||
fromJS({ path: 'posts/title/index.md' }),
|
|
||||||
undefined,
|
|
||||||
),
|
|
||||||
).toEqual('posts');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should resolve field relative media folder', () => {
|
|
||||||
const field = fromJS({ media_folder: '' });
|
|
||||||
expect(
|
|
||||||
selectMediaFolder(
|
|
||||||
{ media_folder: '/static/img' },
|
|
||||||
fromJS({
|
|
||||||
name: 'other',
|
|
||||||
folder: 'other',
|
|
||||||
fields: [field],
|
|
||||||
media_folder: '../',
|
|
||||||
}),
|
|
||||||
fromJS({ path: 'src/other/other.md', data: {} }),
|
|
||||||
field,
|
|
||||||
),
|
|
||||||
).toEqual('src/other');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return collection absolute media folder without leading slash', () => {
|
|
||||||
expect(
|
|
||||||
selectMediaFolder(
|
|
||||||
{ media_folder: '/static/Images' },
|
|
||||||
fromJS({
|
|
||||||
name: 'getting-started',
|
|
||||||
folder: 'src/docs/getting-started',
|
|
||||||
media_folder: '/static/images/docs/getting-started',
|
|
||||||
}),
|
|
||||||
fromJS({}),
|
|
||||||
undefined,
|
|
||||||
),
|
|
||||||
).toEqual('static/images/docs/getting-started');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should compile relative media folder template', () => {
|
|
||||||
const slugConfig = {
|
|
||||||
encoding: 'unicode',
|
|
||||||
clean_accents: false,
|
|
||||||
sanitize_replacement: '-',
|
|
||||||
};
|
|
||||||
|
|
||||||
const entry = fromJS({
|
|
||||||
path: 'content/en/hosting-and-deployment/deployment-with-nanobox.md',
|
|
||||||
data: { title: 'Deployment With NanoBox', category: 'Hosting And Deployment' },
|
|
||||||
});
|
|
||||||
const collection = fromJS({
|
|
||||||
name: 'posts',
|
|
||||||
folder: 'content',
|
|
||||||
media_folder: '../../../{{media_folder}}/{{category}}/{{slug}}',
|
|
||||||
fields: [{ name: 'title', widget: 'string' }],
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(
|
|
||||||
selectMediaFolder(
|
|
||||||
{ media_folder: 'static/media', slug: slugConfig },
|
|
||||||
collection,
|
|
||||||
entry,
|
|
||||||
undefined,
|
|
||||||
),
|
|
||||||
).toEqual('static/media/hosting-and-deployment/deployment-with-nanobox');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should compile absolute media folder template', () => {
|
|
||||||
const slugConfig = {
|
|
||||||
encoding: 'unicode',
|
|
||||||
clean_accents: false,
|
|
||||||
sanitize_replacement: '-',
|
|
||||||
};
|
|
||||||
|
|
||||||
const entry = fromJS({
|
|
||||||
data: { title: 'Overview' },
|
|
||||||
});
|
|
||||||
const collection = fromJS({
|
|
||||||
name: 'extending',
|
|
||||||
folder: 'src/docs/extending',
|
|
||||||
media_folder: '{{media_folder}}/docs/extending',
|
|
||||||
fields: [{ name: 'title', widget: 'string' }],
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(
|
|
||||||
selectMediaFolder(
|
|
||||||
{ media_folder: '/static/images', slug: slugConfig },
|
|
||||||
collection,
|
|
||||||
entry,
|
|
||||||
undefined,
|
|
||||||
),
|
|
||||||
).toEqual('static/images/docs/extending');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should compile field media folder template', () => {
|
|
||||||
const slugConfig = {
|
|
||||||
encoding: 'unicode',
|
|
||||||
clean_accents: false,
|
|
||||||
sanitize_replacement: '-',
|
|
||||||
};
|
|
||||||
|
|
||||||
const entry = fromJS({
|
|
||||||
path: 'content/en/hosting-and-deployment/deployment-with-nanobox.md',
|
|
||||||
data: { title: 'Deployment With NanoBox', category: 'Hosting And Deployment' },
|
|
||||||
});
|
|
||||||
const collection = fromJS({
|
|
||||||
name: 'posts',
|
|
||||||
folder: 'content',
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: 'title',
|
|
||||||
widget: 'string',
|
|
||||||
media_folder: '../../../{{media_folder}}/{{category}}/{{slug}}',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(
|
|
||||||
selectMediaFolder(
|
|
||||||
{ media_folder: 'static/media', slug: slugConfig },
|
|
||||||
collection,
|
|
||||||
entry,
|
|
||||||
collection.get('fields').get(0),
|
|
||||||
),
|
|
||||||
).toEqual('static/media/hosting-and-deployment/deployment-with-nanobox');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle double slashes', () => {
|
|
||||||
const slugConfig = {
|
|
||||||
encoding: 'unicode',
|
|
||||||
clean_accents: false,
|
|
||||||
sanitize_replacement: '-',
|
|
||||||
};
|
|
||||||
|
|
||||||
const entry = fromJS({
|
|
||||||
path: 'content/en/hosting-and-deployment/deployment-with-nanobox.md',
|
|
||||||
data: { title: 'Deployment With NanoBox', category: 'Hosting And Deployment' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const collection = fromJS({
|
|
||||||
name: 'posts',
|
|
||||||
folder: 'content',
|
|
||||||
media_folder: '{{media_folder}}/blog',
|
|
||||||
fields: [{ name: 'title', widget: 'string' }],
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(
|
|
||||||
selectMediaFolder(
|
|
||||||
{ media_folder: '/static/img/', slug: slugConfig },
|
|
||||||
collection,
|
|
||||||
entry,
|
|
||||||
undefined,
|
|
||||||
),
|
|
||||||
).toEqual('static/img/blog');
|
|
||||||
|
|
||||||
expect(
|
|
||||||
selectMediaFolder(
|
|
||||||
{ media_folder: 'static/img/', slug: slugConfig },
|
|
||||||
collection,
|
|
||||||
entry,
|
|
||||||
undefined,
|
|
||||||
),
|
|
||||||
).toEqual('content/en/hosting-and-deployment/static/img/blog');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle file media_folder', () => {
|
|
||||||
expect(
|
|
||||||
selectMediaFolder(
|
|
||||||
{ media_folder: 'static/media' },
|
|
||||||
fromJS({ name: 'posts', files: [{ name: 'index', media_folder: '/static/images/' }] }),
|
|
||||||
fromJS({ path: 'posts/title/index.md', slug: 'index' }),
|
|
||||||
undefined,
|
|
||||||
),
|
|
||||||
).toBe('static/images');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should cascade media_folders', () => {
|
|
||||||
const mainImageField = fromJS({ name: 'main_image' });
|
|
||||||
const logoField = fromJS({ name: 'logo', media_folder: '{{media_folder}}/logos/' });
|
|
||||||
const nestedField3 = fromJS({ name: 'nested', media_folder: '{{media_folder}}/nested3/' });
|
|
||||||
const nestedField2 = fromJS({
|
|
||||||
name: 'nested',
|
|
||||||
media_folder: '{{media_folder}}/nested2/',
|
|
||||||
types: [nestedField3],
|
|
||||||
});
|
|
||||||
const nestedField1 = fromJS({
|
|
||||||
name: 'nested',
|
|
||||||
media_folder: '{{media_folder}}/nested1/',
|
|
||||||
fields: [nestedField2],
|
|
||||||
});
|
|
||||||
|
|
||||||
const args = [
|
|
||||||
{ media_folder: '/static/img' },
|
|
||||||
fromJS({
|
|
||||||
name: 'general',
|
|
||||||
media_folder: '{{media_folder}}/general/',
|
|
||||||
files: [
|
|
||||||
{
|
|
||||||
name: 'customers',
|
|
||||||
media_folder: '{{media_folder}}/customers/',
|
|
||||||
fields: [
|
|
||||||
mainImageField,
|
|
||||||
logoField,
|
|
||||||
{ media_folder: '{{media_folder}}/nested', field: nestedField1 },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
fromJS({ path: 'src/customers/customers.md', slug: 'customers', data: { title: 'title' } }),
|
|
||||||
];
|
|
||||||
|
|
||||||
expect(selectMediaFolder(...args, mainImageField)).toBe('static/img/general/customers');
|
|
||||||
expect(selectMediaFolder(...args, logoField)).toBe('static/img/general/customers/logos');
|
|
||||||
expect(selectMediaFolder(...args, nestedField1)).toBe(
|
|
||||||
'static/img/general/customers/nested/nested1',
|
|
||||||
);
|
|
||||||
expect(selectMediaFolder(...args, nestedField2)).toBe(
|
|
||||||
'static/img/general/customers/nested/nested1/nested2',
|
|
||||||
);
|
|
||||||
expect(selectMediaFolder(...args, nestedField3)).toBe(
|
|
||||||
'static/img/general/customers/nested/nested1/nested2/nested3',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('selectMediaFilePath', () => {
|
|
||||||
it('should return absolute URL as is', () => {
|
|
||||||
expect(selectMediaFilePath(null, null, null, 'https://www.netlify.com/image.png')).toBe(
|
|
||||||
'https://www.netlify.com/image.png',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should resolve path from global media folder for collection with no media folder', () => {
|
|
||||||
expect(
|
|
||||||
selectMediaFilePath(
|
|
||||||
{ media_folder: 'static/media' },
|
|
||||||
fromJS({ name: 'posts', folder: 'posts' }),
|
|
||||||
undefined,
|
|
||||||
'image.png',
|
|
||||||
undefined,
|
|
||||||
),
|
|
||||||
).toBe('static/media/image.png');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should resolve path from collection media folder for collection with media folder', () => {
|
|
||||||
expect(
|
|
||||||
selectMediaFilePath(
|
|
||||||
{ media_folder: 'static/media' },
|
|
||||||
fromJS({ name: 'posts', folder: 'posts', media_folder: '' }),
|
|
||||||
undefined,
|
|
||||||
'image.png',
|
|
||||||
undefined,
|
|
||||||
),
|
|
||||||
).toBe('posts/DRAFT_MEDIA_FILES/image.png');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle relative media_folder', () => {
|
|
||||||
expect(
|
|
||||||
selectMediaFilePath(
|
|
||||||
{ media_folder: 'static/media' },
|
|
||||||
fromJS({ name: 'posts', folder: 'posts', media_folder: '../../static/media/' }),
|
|
||||||
fromJS({ path: 'posts/title/index.md' }),
|
|
||||||
'image.png',
|
|
||||||
undefined,
|
|
||||||
),
|
|
||||||
).toBe('static/media/image.png');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle field media_folder', () => {
|
|
||||||
const field = fromJS({ media_folder: '../../static/media/' });
|
|
||||||
expect(
|
|
||||||
selectMediaFilePath(
|
|
||||||
{ media_folder: 'static/media' },
|
|
||||||
fromJS({ name: 'posts', folder: 'posts', fields: [field] }),
|
|
||||||
fromJS({ path: 'posts/title/index.md' }),
|
|
||||||
'image.png',
|
|
||||||
field,
|
|
||||||
),
|
|
||||||
).toBe('static/media/image.png');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('selectMediaFilePublicPath', () => {
|
|
||||||
it('should return absolute URL as is', () => {
|
|
||||||
expect(selectMediaFilePublicPath(null, null, 'https://www.netlify.com/image.png')).toBe(
|
|
||||||
'https://www.netlify.com/image.png',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should resolve path from public folder for collection with no media folder', () => {
|
|
||||||
expect(
|
|
||||||
selectMediaFilePublicPath(
|
|
||||||
{ public_folder: '/media' },
|
|
||||||
null,
|
|
||||||
'/media/image.png',
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
),
|
|
||||||
).toBe('/media/image.png');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should resolve path from collection public folder for collection with public folder', () => {
|
|
||||||
expect(
|
|
||||||
selectMediaFilePublicPath(
|
|
||||||
{ public_folder: '/media' },
|
|
||||||
fromJS({ name: 'posts', folder: 'posts', public_folder: '' }),
|
|
||||||
'image.png',
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
),
|
|
||||||
).toBe('image.png');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle relative public_folder', () => {
|
|
||||||
expect(
|
|
||||||
selectMediaFilePublicPath(
|
|
||||||
{ public_folder: '/media' },
|
|
||||||
fromJS({ name: 'posts', folder: 'posts', public_folder: '../../static/media/' }),
|
|
||||||
'image.png',
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
),
|
|
||||||
).toBe('../../static/media/image.png');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle absolute public_folder', () => {
|
|
||||||
expect(
|
|
||||||
selectMediaFilePublicPath(
|
|
||||||
{ public_folder: 'https://www.netlify.com/media' },
|
|
||||||
fromJS({
|
|
||||||
name: 'posts',
|
|
||||||
folder: 'posts',
|
|
||||||
public_folder: 'https://www.netlify.com/media',
|
|
||||||
}),
|
|
||||||
'image.png',
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
),
|
|
||||||
).toBe('https://www.netlify.com/media/image.png');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should compile collection public folder template', () => {
|
|
||||||
const slugConfig = {
|
|
||||||
encoding: 'unicode',
|
|
||||||
clean_accents: false,
|
|
||||||
sanitize_replacement: '-',
|
|
||||||
};
|
|
||||||
|
|
||||||
const entry = fromJS({
|
|
||||||
path: 'content/en/hosting-and-deployment/deployment-with-nanobox.md',
|
|
||||||
data: { title: 'Deployment With NanoBox', category: 'Hosting And Deployment' },
|
|
||||||
});
|
|
||||||
const collection = fromJS({
|
|
||||||
name: 'posts',
|
|
||||||
folder: 'content',
|
|
||||||
public_folder: '/{{public_folder}}/{{category}}/{{slug}}',
|
|
||||||
fields: [{ name: 'title', widget: 'string' }],
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(
|
|
||||||
selectMediaFilePublicPath(
|
|
||||||
{ public_folder: 'static/media', slug: slugConfig },
|
|
||||||
collection,
|
|
||||||
'image.png',
|
|
||||||
entry,
|
|
||||||
undefined,
|
|
||||||
),
|
|
||||||
).toEqual('/static/media/hosting-and-deployment/deployment-with-nanobox/image.png');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should compile field public folder template', () => {
|
|
||||||
const slugConfig = {
|
|
||||||
encoding: 'unicode',
|
|
||||||
clean_accents: false,
|
|
||||||
sanitize_replacement: '-',
|
|
||||||
};
|
|
||||||
|
|
||||||
const entry = fromJS({
|
|
||||||
path: 'content/en/hosting-and-deployment/deployment-with-nanobox.md',
|
|
||||||
data: { title: 'Deployment With NanoBox', category: 'Hosting And Deployment' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const field = fromJS({
|
|
||||||
name: 'title',
|
|
||||||
widget: 'string',
|
|
||||||
public_folder: '/{{public_folder}}/{{category}}/{{slug}}',
|
|
||||||
});
|
|
||||||
const collection = fromJS({
|
|
||||||
name: 'posts',
|
|
||||||
folder: 'content',
|
|
||||||
fields: [field],
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(
|
|
||||||
selectMediaFilePublicPath(
|
|
||||||
{ public_folder: 'static/media', slug: slugConfig },
|
|
||||||
collection,
|
|
||||||
'image.png',
|
|
||||||
entry,
|
|
||||||
field,
|
|
||||||
),
|
|
||||||
).toEqual('/static/media/hosting-and-deployment/deployment-with-nanobox/image.png');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle double slashes', () => {
|
|
||||||
const slugConfig = {
|
|
||||||
encoding: 'unicode',
|
|
||||||
clean_accents: false,
|
|
||||||
sanitize_replacement: '-',
|
|
||||||
};
|
|
||||||
|
|
||||||
const entry = fromJS({
|
|
||||||
path: 'content/en/hosting-and-deployment/deployment-with-nanobox.md',
|
|
||||||
data: { title: 'Deployment With NanoBox', category: 'Hosting And Deployment' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const field = fromJS({
|
|
||||||
name: 'title',
|
|
||||||
widget: 'string',
|
|
||||||
public_folder: '/{{public_folder}}/{{category}}/{{slug}}',
|
|
||||||
});
|
|
||||||
const collection = fromJS({
|
|
||||||
name: 'posts',
|
|
||||||
folder: 'content',
|
|
||||||
fields: [field],
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(
|
|
||||||
selectMediaFilePublicPath(
|
|
||||||
{ public_folder: 'static/media/', slug: slugConfig },
|
|
||||||
collection,
|
|
||||||
'image.png',
|
|
||||||
entry,
|
|
||||||
field,
|
|
||||||
),
|
|
||||||
).toEqual('/static/media/hosting-and-deployment/deployment-with-nanobox/image.png');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle file public_folder', () => {
|
|
||||||
const entry = fromJS({
|
|
||||||
path: 'src/posts/index.md',
|
|
||||||
slug: 'index',
|
|
||||||
});
|
|
||||||
|
|
||||||
const collection = fromJS({
|
|
||||||
name: 'posts',
|
|
||||||
files: [
|
|
||||||
{
|
|
||||||
name: 'index',
|
|
||||||
public_folder: '/images',
|
|
||||||
fields: [{ name: 'title', widget: 'string' }],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(
|
|
||||||
selectMediaFilePublicPath(
|
|
||||||
{ public_folder: 'static/media/' },
|
|
||||||
collection,
|
|
||||||
'image.png',
|
|
||||||
entry,
|
|
||||||
undefined,
|
|
||||||
),
|
|
||||||
).toBe('/images/image.png');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('selectEntries', () => {
|
|
||||||
it('should return all entries', () => {
|
|
||||||
const state = fromJS({
|
|
||||||
entities: {
|
|
||||||
'posts.1': { slug: '1' },
|
|
||||||
'posts.2': { slug: '2' },
|
|
||||||
'posts.3': { slug: '3' },
|
|
||||||
'posts.4': { slug: '4' },
|
|
||||||
},
|
|
||||||
pages: { posts: { ids: ['1', '2', '3', '4'] } },
|
|
||||||
});
|
|
||||||
const collection = fromJS({
|
|
||||||
name: 'posts',
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(selectEntries(state, collection)).toEqual(
|
|
||||||
fromJS([{ slug: '1' }, { slug: '2' }, { slug: '3' }, { slug: '4' }]),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return sorted entries entries by field', () => {
|
|
||||||
const state = fromJS({
|
|
||||||
entities: {
|
|
||||||
'posts.1': { slug: '1', data: { title: '1' } },
|
|
||||||
'posts.2': { slug: '2', data: { title: '2' } },
|
|
||||||
'posts.3': { slug: '3', data: { title: '3' } },
|
|
||||||
'posts.4': { slug: '4', data: { title: '4' } },
|
|
||||||
},
|
|
||||||
pages: { posts: { ids: ['1', '2', '3', '4'] } },
|
|
||||||
sort: { posts: { title: { key: 'title', direction: 'Descending' } } },
|
|
||||||
});
|
|
||||||
const collection = fromJS({
|
|
||||||
name: 'posts',
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(selectEntries(state, collection)).toEqual(
|
|
||||||
fromJS([
|
|
||||||
{ slug: '4', data: { title: '4' } },
|
|
||||||
{ slug: '3', data: { title: '3' } },
|
|
||||||
{ slug: '2', data: { title: '2' } },
|
|
||||||
{ slug: '1', data: { title: '1' } },
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return sorted entries entries by nested field', () => {
|
|
||||||
const state = fromJS({
|
|
||||||
entities: {
|
|
||||||
'posts.1': { slug: '1', data: { title: '1', nested: { date: 4 } } },
|
|
||||||
'posts.2': { slug: '2', data: { title: '2', nested: { date: 3 } } },
|
|
||||||
'posts.3': { slug: '3', data: { title: '3', nested: { date: 2 } } },
|
|
||||||
'posts.4': { slug: '4', data: { title: '4', nested: { date: 1 } } },
|
|
||||||
},
|
|
||||||
pages: { posts: { ids: ['1', '2', '3', '4'] } },
|
|
||||||
sort: { posts: { title: { key: 'nested.date', direction: 'Ascending' } } },
|
|
||||||
});
|
|
||||||
const collection = fromJS({
|
|
||||||
name: 'posts',
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(selectEntries(state, collection)).toEqual(
|
|
||||||
fromJS([
|
|
||||||
{ slug: '4', data: { title: '4', nested: { date: 1 } } },
|
|
||||||
{ slug: '3', data: { title: '3', nested: { date: 2 } } },
|
|
||||||
{ slug: '2', data: { title: '2', nested: { date: 3 } } },
|
|
||||||
{ slug: '1', data: { title: '1', nested: { date: 4 } } },
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return filtered entries entries by field', () => {
|
|
||||||
const state = fromJS({
|
|
||||||
entities: {
|
|
||||||
'posts.1': { slug: '1', data: { title: '1' } },
|
|
||||||
'posts.2': { slug: '2', data: { title: '2' } },
|
|
||||||
'posts.3': { slug: '3', data: { title: '3' } },
|
|
||||||
'posts.4': { slug: '4', data: { title: '4' } },
|
|
||||||
},
|
|
||||||
pages: { posts: { ids: ['1', '2', '3', '4'] } },
|
|
||||||
filter: { posts: { title__1: { field: 'title', pattern: '4', active: true } } },
|
|
||||||
});
|
|
||||||
const collection = fromJS({
|
|
||||||
name: 'posts',
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(selectEntries(state, collection)).toEqual(fromJS([{ slug: '4', data: { title: '4' } }]));
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return filtered entries entries by nested field', () => {
|
|
||||||
const state = fromJS({
|
|
||||||
entities: {
|
|
||||||
'posts.1': { slug: '1', data: { title: '1', nested: { draft: true } } },
|
|
||||||
'posts.2': { slug: '2', data: { title: '2', nested: { draft: true } } },
|
|
||||||
'posts.3': { slug: '3', data: { title: '3', nested: { draft: false } } },
|
|
||||||
'posts.4': { slug: '4', data: { title: '4', nested: { draft: false } } },
|
|
||||||
},
|
|
||||||
pages: { posts: { ids: ['1', '2', '3', '4'] } },
|
|
||||||
filter: {
|
|
||||||
posts: { 'nested.draft__false': { field: 'nested.draft', pattern: false, active: true } },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const collection = fromJS({
|
|
||||||
name: 'posts',
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(selectEntries(state, collection)).toEqual(
|
|
||||||
fromJS([
|
|
||||||
{ slug: '3', data: { title: '3', nested: { draft: false } } },
|
|
||||||
{ slug: '4', data: { title: '4', nested: { draft: false } } },
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,198 +0,0 @@
|
|||||||
import { Map, fromJS } from 'immutable';
|
|
||||||
|
|
||||||
import * as actions from '../../actions/entries';
|
|
||||||
import reducer from '../entryDraft';
|
|
||||||
|
|
||||||
jest.mock('uuid/v4', () => jest.fn(() => '1'));
|
|
||||||
|
|
||||||
const initialState = Map({
|
|
||||||
entry: Map(),
|
|
||||||
fieldsMetaData: Map(),
|
|
||||||
fieldsErrors: Map(),
|
|
||||||
hasChanged: false,
|
|
||||||
key: '',
|
|
||||||
});
|
|
||||||
|
|
||||||
const entry = {
|
|
||||||
collection: 'posts',
|
|
||||||
slug: 'slug',
|
|
||||||
path: 'content/blog/art-and-wine-festival.md',
|
|
||||||
partial: false,
|
|
||||||
raw: '',
|
|
||||||
data: {},
|
|
||||||
metaData: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('entryDraft reducer', () => {
|
|
||||||
describe('DRAFT_CREATE_FROM_ENTRY', () => {
|
|
||||||
it('should create draft from the entry', () => {
|
|
||||||
const state = reducer(initialState, actions.createDraftFromEntry(fromJS(entry)));
|
|
||||||
expect(state).toEqual(
|
|
||||||
fromJS({
|
|
||||||
entry: {
|
|
||||||
...entry,
|
|
||||||
newRecord: false,
|
|
||||||
},
|
|
||||||
fieldsMetaData: Map(),
|
|
||||||
fieldsErrors: Map(),
|
|
||||||
hasChanged: false,
|
|
||||||
key: '1',
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('DRAFT_CREATE_EMPTY', () => {
|
|
||||||
it('should create a new draft ', () => {
|
|
||||||
const state = reducer(initialState, actions.emptyDraftCreated(fromJS(entry)));
|
|
||||||
expect(state).toEqual(
|
|
||||||
fromJS({
|
|
||||||
entry: {
|
|
||||||
...entry,
|
|
||||||
newRecord: true,
|
|
||||||
},
|
|
||||||
fieldsMetaData: Map(),
|
|
||||||
fieldsErrors: Map(),
|
|
||||||
hasChanged: false,
|
|
||||||
key: '1',
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('DRAFT_DISCARD', () => {
|
|
||||||
it('should discard the draft and return initial state', () => {
|
|
||||||
expect(reducer(initialState, actions.discardDraft())).toEqual(initialState);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('persisting', () => {
|
|
||||||
let initialState;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
initialState = fromJS({
|
|
||||||
entities: {
|
|
||||||
'posts.slug': {
|
|
||||||
collection: 'posts',
|
|
||||||
slug: 'slug',
|
|
||||||
path: 'content/blog/art-and-wine-festival.md',
|
|
||||||
partial: false,
|
|
||||||
raw: '',
|
|
||||||
data: {},
|
|
||||||
metaData: null,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
pages: {},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle persisting request', () => {
|
|
||||||
const newState = reducer(
|
|
||||||
initialState,
|
|
||||||
actions.entryPersisting(Map({ name: 'posts' }), Map({ slug: 'slug' })),
|
|
||||||
);
|
|
||||||
expect(newState.getIn(['entry', 'isPersisting'])).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle persisting success', () => {
|
|
||||||
let newState = reducer(
|
|
||||||
initialState,
|
|
||||||
actions.entryPersisting(Map({ name: 'posts' }), Map({ slug: 'slug' })),
|
|
||||||
);
|
|
||||||
newState = reducer(
|
|
||||||
newState,
|
|
||||||
actions.entryPersisted(Map({ name: 'posts' }), Map({ slug: 'slug' })),
|
|
||||||
);
|
|
||||||
expect(newState.getIn(['entry', 'isPersisting'])).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle persisting error', () => {
|
|
||||||
let newState = reducer(
|
|
||||||
initialState,
|
|
||||||
actions.entryPersisting(Map({ name: 'posts' }), Map({ slug: 'slug' })),
|
|
||||||
);
|
|
||||||
newState = reducer(
|
|
||||||
newState,
|
|
||||||
actions.entryPersistFail(Map({ name: 'posts' }), Map({ slug: 'slug' }), 'Error message'),
|
|
||||||
);
|
|
||||||
expect(newState.getIn(['entry', 'isPersisting'])).toBeUndefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('REMOVE_DRAFT_ENTRY_MEDIA_FILE', () => {
|
|
||||||
it('should remove a media file', () => {
|
|
||||||
const actualState = reducer(
|
|
||||||
initialState.setIn(['entry', 'mediaFiles'], fromJS([{ id: '1' }, { id: '2' }])),
|
|
||||||
actions.removeDraftEntryMediaFile({ id: '1' }),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(actualState.toJS()).toEqual({
|
|
||||||
entry: { mediaFiles: [{ id: '2' }] },
|
|
||||||
fieldsMetaData: {},
|
|
||||||
fieldsErrors: {},
|
|
||||||
hasChanged: true,
|
|
||||||
key: '',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('ADD_DRAFT_ENTRY_MEDIA_FILE', () => {
|
|
||||||
it('should overwrite an existing media file', () => {
|
|
||||||
const actualState = reducer(
|
|
||||||
initialState.setIn(['entry', 'mediaFiles'], fromJS([{ id: '1', name: 'old' }])),
|
|
||||||
actions.addDraftEntryMediaFile({ id: '1', name: 'new' }),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(actualState.toJS()).toEqual({
|
|
||||||
entry: { mediaFiles: [{ id: '1', name: 'new' }] },
|
|
||||||
fieldsMetaData: {},
|
|
||||||
fieldsErrors: {},
|
|
||||||
hasChanged: true,
|
|
||||||
key: '',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('DRAFT_CREATE_FROM_LOCAL_BACKUP', () => {
|
|
||||||
it('should create draft from local backup', () => {
|
|
||||||
const localBackup = Map({ entry: fromJS({ ...entry, mediaFiles: [{ id: '1' }] }) });
|
|
||||||
|
|
||||||
const actualState = reducer(initialState.set('localBackup', localBackup), {
|
|
||||||
type: actions.DRAFT_CREATE_FROM_LOCAL_BACKUP,
|
|
||||||
});
|
|
||||||
expect(actualState.toJS()).toEqual({
|
|
||||||
entry: {
|
|
||||||
...entry,
|
|
||||||
mediaFiles: [{ id: '1' }],
|
|
||||||
newRecord: false,
|
|
||||||
},
|
|
||||||
fieldsMetaData: {},
|
|
||||||
fieldsErrors: {},
|
|
||||||
hasChanged: true,
|
|
||||||
key: '1',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('DRAFT_LOCAL_BACKUP_RETRIEVED', () => {
|
|
||||||
it('should set local backup', () => {
|
|
||||||
const mediaFiles = [{ id: '1' }];
|
|
||||||
|
|
||||||
const actualState = reducer(
|
|
||||||
initialState,
|
|
||||||
actions.localBackupRetrieved({ ...entry, mediaFiles }),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(actualState.toJS()).toEqual({
|
|
||||||
entry: {},
|
|
||||||
fieldsMetaData: {},
|
|
||||||
fieldsErrors: {},
|
|
||||||
hasChanged: false,
|
|
||||||
localBackup: {
|
|
||||||
entry: { ...entry, mediaFiles: [{ id: '1' }] },
|
|
||||||
},
|
|
||||||
key: '',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,43 +0,0 @@
|
|||||||
import { USE_OPEN_AUTHORING } from '../../actions/auth';
|
|
||||||
import {
|
|
||||||
DEPLOY_PREVIEW_REQUEST,
|
|
||||||
DEPLOY_PREVIEW_SUCCESS,
|
|
||||||
DEPLOY_PREVIEW_FAILURE,
|
|
||||||
} from '../../actions/deploys';
|
|
||||||
import { ENTRY_REQUEST, ENTRY_SUCCESS, ENTRY_FAILURE } from '../../actions/entries';
|
|
||||||
import reducer from '../globalUI';
|
|
||||||
|
|
||||||
describe('globalUI', () => {
|
|
||||||
it('should set isFetching to true on entry request', () => {
|
|
||||||
expect(reducer({ isFetching: false }, { type: ENTRY_REQUEST })).toEqual({ isFetching: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should set isFetching to false on entry success', () => {
|
|
||||||
expect(reducer({ isFetching: true }, { type: ENTRY_SUCCESS })).toEqual({ isFetching: false });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should set isFetching to false on entry failure', () => {
|
|
||||||
expect(reducer({ isFetching: true }, { type: ENTRY_FAILURE })).toEqual({ isFetching: false });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not change state on deploy preview request', () => {
|
|
||||||
const state = { isFetching: false };
|
|
||||||
expect(reducer(state, { type: DEPLOY_PREVIEW_REQUEST })).toBe(state);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not change state on deploy preview success', () => {
|
|
||||||
const state = { isFetching: true };
|
|
||||||
expect(reducer(state, { type: DEPLOY_PREVIEW_SUCCESS })).toBe(state);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not change state on deploy preview failure', () => {
|
|
||||||
const state = { isFetching: true };
|
|
||||||
expect(reducer(state, { type: DEPLOY_PREVIEW_FAILURE })).toBe(state);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should set useOpenAuthoring to true on USE_OPEN_AUTHORING', () => {
|
|
||||||
expect(reducer({ useOpenAuthoring: false }, { type: USE_OPEN_AUTHORING })).toEqual({
|
|
||||||
useOpenAuthoring: true,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,76 +0,0 @@
|
|||||||
import integrations from '../integrations';
|
|
||||||
import { CONFIG_SUCCESS } from '../../actions/config';
|
|
||||||
import { FOLDER } from '../../constants/collectionTypes';
|
|
||||||
|
|
||||||
import type { ConfigAction } from '../../actions/config';
|
|
||||||
|
|
||||||
describe('integrations', () => {
|
|
||||||
it('should return default state when no integrations', () => {
|
|
||||||
const result = integrations(null, {
|
|
||||||
type: CONFIG_SUCCESS,
|
|
||||||
payload: { integrations: [] },
|
|
||||||
} as ConfigAction);
|
|
||||||
expect(result && result.toJS()).toEqual({
|
|
||||||
providers: {},
|
|
||||||
hooks: {},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return hooks and providers map when has integrations', () => {
|
|
||||||
const result = integrations(null, {
|
|
||||||
type: CONFIG_SUCCESS,
|
|
||||||
payload: {
|
|
||||||
integrations: [
|
|
||||||
{
|
|
||||||
hooks: ['listEntries'],
|
|
||||||
collections: '*',
|
|
||||||
provider: 'algolia',
|
|
||||||
applicationID: 'applicationID',
|
|
||||||
apiKey: 'apiKey',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
hooks: ['listEntries'],
|
|
||||||
collections: ['posts'],
|
|
||||||
provider: 'algolia',
|
|
||||||
applicationID: 'applicationID',
|
|
||||||
apiKey: 'apiKey',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
hooks: ['assetStore'],
|
|
||||||
provider: 'assetStore',
|
|
||||||
getSignedFormURL: 'https://asset.store.com/signedUrl',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
collections: [
|
|
||||||
{ name: 'posts', label: 'Posts', type: FOLDER },
|
|
||||||
{ name: 'pages', label: 'Pages', type: FOLDER },
|
|
||||||
{ name: 'faq', label: 'FAQ', type: FOLDER },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
} as ConfigAction);
|
|
||||||
|
|
||||||
expect(result && result.toJS()).toEqual({
|
|
||||||
providers: {
|
|
||||||
algolia: {
|
|
||||||
applicationID: 'applicationID',
|
|
||||||
apiKey: 'apiKey',
|
|
||||||
},
|
|
||||||
assetStore: {
|
|
||||||
getSignedFormURL: 'https://asset.store.com/signedUrl',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
hooks: {
|
|
||||||
posts: {
|
|
||||||
listEntries: 'algolia',
|
|
||||||
},
|
|
||||||
pages: {
|
|
||||||
listEntries: 'algolia',
|
|
||||||
},
|
|
||||||
faq: {
|
|
||||||
listEntries: 'algolia',
|
|
||||||
},
|
|
||||||
assetStore: 'assetStore',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,154 +0,0 @@
|
|||||||
import { Map, fromJS } from 'immutable';
|
|
||||||
|
|
||||||
import { mediaDeleted } from '../../actions/mediaLibrary';
|
|
||||||
import mediaLibrary, {
|
|
||||||
selectMediaFiles,
|
|
||||||
selectMediaFileByPath,
|
|
||||||
selectMediaDisplayURL,
|
|
||||||
} from '../mediaLibrary';
|
|
||||||
|
|
||||||
jest.mock('uuid/v4');
|
|
||||||
jest.mock('../entries');
|
|
||||||
jest.mock('../');
|
|
||||||
|
|
||||||
describe('mediaLibrary', () => {
|
|
||||||
it('should remove media file by key', () => {
|
|
||||||
expect(
|
|
||||||
mediaLibrary(
|
|
||||||
Map({
|
|
||||||
files: [{ key: 'key1' }, { key: 'key2' }],
|
|
||||||
}),
|
|
||||||
mediaDeleted({ key: 'key1' }),
|
|
||||||
),
|
|
||||||
).toEqual(
|
|
||||||
Map({
|
|
||||||
isDeleting: false,
|
|
||||||
files: [{ key: 'key2' }],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should remove media file by id', () => {
|
|
||||||
expect(
|
|
||||||
mediaLibrary(
|
|
||||||
Map({
|
|
||||||
files: [{ id: 'id1' }, { id: 'id2' }],
|
|
||||||
}),
|
|
||||||
mediaDeleted({ id: 'id1' }),
|
|
||||||
),
|
|
||||||
).toEqual(
|
|
||||||
Map({
|
|
||||||
isDeleting: false,
|
|
||||||
files: [{ id: 'id2' }],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should select draft media files from field when editing a draft', () => {
|
|
||||||
const { selectEditingDraft, selectMediaFolder } = require('../../reducers/entries');
|
|
||||||
|
|
||||||
selectEditingDraft.mockReturnValue(true);
|
|
||||||
selectMediaFolder.mockReturnValue('/static/images/posts/logos');
|
|
||||||
|
|
||||||
const imageField = fromJS({ name: 'image' });
|
|
||||||
const collection = fromJS({ fields: [imageField] });
|
|
||||||
const entry = fromJS({
|
|
||||||
collection: 'posts',
|
|
||||||
mediaFiles: [
|
|
||||||
{ id: 1, path: '/static/images/posts/logos/logo.png' },
|
|
||||||
{ id: 2, path: '/static/images/posts/general/image.png' },
|
|
||||||
{ id: 3, path: '/static/images/posts/index.png' },
|
|
||||||
],
|
|
||||||
data: {},
|
|
||||||
});
|
|
||||||
const state = {
|
|
||||||
config: {},
|
|
||||||
collections: fromJS({ posts: collection }),
|
|
||||||
entryDraft: fromJS({
|
|
||||||
entry,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(selectMediaFiles(state, imageField)).toEqual([
|
|
||||||
{ id: 1, key: 1, path: '/static/images/posts/logos/logo.png' },
|
|
||||||
]);
|
|
||||||
|
|
||||||
expect(selectMediaFolder).toHaveBeenCalledWith(state.config, collection, entry, imageField);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should select draft media files from collection when editing a draft', () => {
|
|
||||||
const { selectEditingDraft, selectMediaFolder } = require('../../reducers/entries');
|
|
||||||
|
|
||||||
selectEditingDraft.mockReturnValue(true);
|
|
||||||
selectMediaFolder.mockReturnValue('/static/images/posts');
|
|
||||||
|
|
||||||
const imageField = fromJS({ name: 'image' });
|
|
||||||
const collection = fromJS({ fields: [imageField] });
|
|
||||||
const entry = fromJS({
|
|
||||||
collection: 'posts',
|
|
||||||
mediaFiles: [
|
|
||||||
{ id: 1, path: '/static/images/posts/logos/logo.png' },
|
|
||||||
{ id: 2, path: '/static/images/posts/general/image.png' },
|
|
||||||
{ id: 3, path: '/static/images/posts/index.png' },
|
|
||||||
],
|
|
||||||
data: {},
|
|
||||||
});
|
|
||||||
const state = {
|
|
||||||
config: {},
|
|
||||||
collections: fromJS({ posts: collection }),
|
|
||||||
entryDraft: fromJS({
|
|
||||||
entry,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(selectMediaFiles(state, imageField)).toEqual([
|
|
||||||
{ id: 3, key: 3, path: '/static/images/posts/index.png' },
|
|
||||||
]);
|
|
||||||
|
|
||||||
expect(selectMediaFolder).toHaveBeenCalledWith(state.config, collection, entry, imageField);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should select global media files when not editing a draft', () => {
|
|
||||||
const { selectEditingDraft } = require('../../reducers/entries');
|
|
||||||
|
|
||||||
selectEditingDraft.mockReturnValue(false);
|
|
||||||
|
|
||||||
const state = {
|
|
||||||
mediaLibrary: Map({ files: [{ id: 1 }] }),
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(selectMediaFiles(state)).toEqual([{ id: 1 }]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should select global media files when not using asset store integration', () => {
|
|
||||||
const { selectIntegration } = require('../../reducers');
|
|
||||||
|
|
||||||
selectIntegration.mockReturnValue({});
|
|
||||||
|
|
||||||
const state = {
|
|
||||||
mediaLibrary: Map({ files: [{ id: 1 }] }),
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(selectMediaFiles(state)).toEqual([{ id: 1 }]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return media file by path', () => {
|
|
||||||
const { selectEditingDraft } = require('../../reducers/entries');
|
|
||||||
|
|
||||||
selectEditingDraft.mockReturnValue(false);
|
|
||||||
|
|
||||||
const state = {
|
|
||||||
mediaLibrary: Map({ files: [{ id: 1, path: 'path' }] }),
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(selectMediaFileByPath(state, 'path')).toEqual({ id: 1, path: 'path' });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return media display URL state', () => {
|
|
||||||
const state = {
|
|
||||||
mediaLibrary: fromJS({ displayURLs: { id: { url: 'url' } } }),
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(selectMediaDisplayURL(state, 'id')).toEqual(Map({ url: 'url' }));
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,49 +0,0 @@
|
|||||||
import {
|
|
||||||
addAssets,
|
|
||||||
addAsset,
|
|
||||||
removeAsset,
|
|
||||||
loadAssetRequest,
|
|
||||||
loadAssetSuccess,
|
|
||||||
loadAssetFailure,
|
|
||||||
} from '../../actions/media';
|
|
||||||
import reducer from '../medias';
|
|
||||||
import { createAssetProxy } from '../../valueObjects/AssetProxy';
|
|
||||||
|
|
||||||
describe('medias', () => {
|
|
||||||
const asset = createAssetProxy({ url: 'url', path: 'path' });
|
|
||||||
|
|
||||||
it('should add assets', () => {
|
|
||||||
expect(reducer({}, addAssets([asset]))).toEqual({
|
|
||||||
path: { asset, isLoading: false, error: null },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should add asset', () => {
|
|
||||||
expect(reducer({}, addAsset(asset))).toEqual({
|
|
||||||
path: { asset, isLoading: false, error: null },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should remove asset', () => {
|
|
||||||
expect(
|
|
||||||
reducer({ [asset.path]: { asset, isLoading: false, error: null } }, removeAsset(asset.path)),
|
|
||||||
).toEqual({});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should mark asset as loading', () => {
|
|
||||||
expect(reducer({}, loadAssetRequest(asset.path))).toEqual({ path: { isLoading: true } });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should mark asset as not loading', () => {
|
|
||||||
expect(reducer({}, loadAssetSuccess(asset.path))).toEqual({
|
|
||||||
path: { isLoading: false, error: null },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should set loading error', () => {
|
|
||||||
const error = new Error('some error');
|
|
||||||
expect(reducer({}, loadAssetFailure(asset.path, error))).toEqual({
|
|
||||||
path: { isLoading: false, error },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,49 +0,0 @@
|
|||||||
import { createHashHistory } from 'history';
|
|
||||||
import { mocked } from 'ts-jest/utils';
|
|
||||||
|
|
||||||
import type { History } from 'history';
|
|
||||||
|
|
||||||
jest.mock('history');
|
|
||||||
|
|
||||||
const history = { push: jest.fn(), replace: jest.fn() } as unknown as History;
|
|
||||||
const mockedCreateHashHistory = mocked(createHashHistory);
|
|
||||||
mockedCreateHashHistory.mockReturnValue(history);
|
|
||||||
|
|
||||||
describe('history', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.clearAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('navigateToCollection', () => {
|
|
||||||
it('should push route', () => {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
||||||
const { navigateToCollection } = require('../history');
|
|
||||||
|
|
||||||
navigateToCollection('posts');
|
|
||||||
expect(history.push).toHaveBeenCalledTimes(1);
|
|
||||||
expect(history.push).toHaveBeenCalledWith('/collections/posts');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('navigateToNewEntry', () => {
|
|
||||||
it('should replace route', () => {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
||||||
const { navigateToNewEntry } = require('../history');
|
|
||||||
|
|
||||||
navigateToNewEntry('posts');
|
|
||||||
expect(history.replace).toHaveBeenCalledTimes(1);
|
|
||||||
expect(history.replace).toHaveBeenCalledWith('/collections/posts/new');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('navigateToEntry', () => {
|
|
||||||
it('should replace route', () => {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
||||||
const { navigateToEntry } = require('../history');
|
|
||||||
|
|
||||||
navigateToEntry('posts', 'index');
|
|
||||||
expect(history.replace).toHaveBeenCalledTimes(1);
|
|
||||||
expect(history.replace).toHaveBeenCalledWith('/collections/posts/entries/index');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,129 +0,0 @@
|
|||||||
const webpack = require('webpack');
|
|
||||||
const path = require('path');
|
|
||||||
const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin');
|
|
||||||
const { flatMap } = require('lodash');
|
|
||||||
|
|
||||||
const isProduction = process.env.NODE_ENV === 'production';
|
|
||||||
const isTest = process.env.NODE_ENV === 'test';
|
|
||||||
|
|
||||||
const pkg = require('./package.json');
|
|
||||||
|
|
||||||
const devServerPort = parseInt(process.env.SIMPLE_CMS_DEV_SERVER_PORT || `${8080}`);
|
|
||||||
|
|
||||||
function moduleNameToPath(libName) {
|
|
||||||
return path.resolve(__dirname, 'node_modules', libName);
|
|
||||||
}
|
|
||||||
|
|
||||||
function rules() {
|
|
||||||
return {
|
|
||||||
js: () => ({
|
|
||||||
test: /\.(ts|js)x?$/,
|
|
||||||
exclude: /node_modules/,
|
|
||||||
use: {
|
|
||||||
loader: 'babel-loader',
|
|
||||||
options: {
|
|
||||||
rootMode: 'upward',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
css: () => [
|
|
||||||
{
|
|
||||||
test: /\.css$/,
|
|
||||||
include: ['ol', 'react-datetime', 'codemirror'].map(moduleNameToPath),
|
|
||||||
use: ['to-string-loader', 'css-loader'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
svg: () => ({
|
|
||||||
test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
|
|
||||||
exclude: [/node_modules/],
|
|
||||||
use: [
|
|
||||||
{
|
|
||||||
loader: 'babel-loader',
|
|
||||||
options: {
|
|
||||||
rootMode: 'upward',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
loader: 'react-svg-loader',
|
|
||||||
options: {
|
|
||||||
jsx: true, // true outputs JSX tags
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function plugins() {
|
|
||||||
return {
|
|
||||||
ignoreEsprima: () => new webpack.IgnorePlugin(/^esprima$/, /js-yaml/),
|
|
||||||
ignoreMomentOptionalDeps: () => new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function stats() {
|
|
||||||
if (isProduction) {
|
|
||||||
return {
|
|
||||||
builtAt: false,
|
|
||||||
chunks: false,
|
|
||||||
colors: true,
|
|
||||||
entrypoints: false,
|
|
||||||
errorDetails: false,
|
|
||||||
hash: false,
|
|
||||||
modules: false,
|
|
||||||
timings: false,
|
|
||||||
version: false,
|
|
||||||
warnings: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
all: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
context: process.cwd(),
|
|
||||||
mode: isProduction ? 'production' : 'development',
|
|
||||||
entry: './src',
|
|
||||||
output: {
|
|
||||||
path: path.resolve(process.cwd(), 'dist'),
|
|
||||||
filename: `simple-cms-core.js`,
|
|
||||||
library: 'SimpleCmsCore',
|
|
||||||
libraryTarget: 'umd',
|
|
||||||
libraryExport: 'SimpleCmsCore',
|
|
||||||
umdNamedDefine: true,
|
|
||||||
globalObject: 'window',
|
|
||||||
},
|
|
||||||
module: {
|
|
||||||
rules: flatMap(Object.values(rules()), rule => rule()),
|
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'],
|
|
||||||
alias: {
|
|
||||||
moment$: 'moment/moment.js',
|
|
||||||
'react-dom': '@hot-loader/react-dom',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
devtool: isTest ? '' : 'source-map',
|
|
||||||
target: 'web',
|
|
||||||
stats: stats(),
|
|
||||||
plugins: [
|
|
||||||
...Object.values(plugins()).map(plugin => plugin()),
|
|
||||||
new webpack.DefinePlugin({
|
|
||||||
SIMPLE_CMS_CORE_VERSION: JSON.stringify(`${pkg.version}${isProduction ? '' : '-dev'}`),
|
|
||||||
}),
|
|
||||||
new FriendlyErrorsWebpackPlugin({
|
|
||||||
compilationSuccessInfo: {
|
|
||||||
messages: [`Simple CMS is now running at http://localhost:${devServerPort}`],
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
devServer: {
|
|
||||||
contentBase: './dev-test',
|
|
||||||
watchContentBase: true,
|
|
||||||
publicPath: './dist',
|
|
||||||
quiet: true,
|
|
||||||
host: '0.0.0.0',
|
|
||||||
port: devServerPort,
|
|
||||||
},
|
|
||||||
};
|
|
Loading…
x
Reference in New Issue
Block a user