31 |
32 | `;
33 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
13 | * or implied. See the License for the specific language governing
14 | * permissions and limitations under the License.
15 | */
16 |
17 | module.exports = {
18 | presets: ['amex'],
19 | };
20 |
--------------------------------------------------------------------------------
/jest.setup.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
13 | * or implied. See the License for the specific language governing
14 | * permissions and limitations under the License.
15 | */
16 |
17 | require('@babel/polyfill');
18 | require('whatwg-fetch');
19 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "extends": ["amex", "plugin:prettier/recommended", "prettier/react"],
4 | "overrides": [
5 | {
6 | "files": ["**/__tests__/**", "**/__mocks__/**", "*.spec.{js,jsx}", "jest.setup.js"],
7 | "extends": ["amex/test", "plugin:prettier/recommended", "prettier/react"],
8 | "rules": {
9 | "import/no-extraneous-dependencies": 0,
10 | "no-restricted-globals": 0
11 | }
12 | },
13 | {
14 | "files": ["*.md"],
15 | "rules": {
16 | "no-useless-constructor": 0,
17 | "global-require": 0
18 | }
19 | },
20 | {
21 | "files": ["packages/parrot-devtools/**"],
22 | "env": {
23 | "webextensions": true
24 | }
25 | }
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/packages/parrot-core/src/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | export { default } from './Parrot';
16 | export getParams from './utils/getParams';
17 |
--------------------------------------------------------------------------------
/packages/parrot-devtools/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | ### Chrome Extension
7 |
8 | - Install [the extension](https://chrome.google.com/webstore/detail/parrot-devtools/jckchajdleibnohnphddbiglgpjpbffn).
9 | - Navigate to the Parrot tab of your devtools panel.
10 |
11 | ### Firefox Add-On
12 |
13 | - Install [the add-on](https://github.com/americanexpress/parrot/blob/main/packages/parrot-devtools/parrot-devtools-extension.zip).
14 | - Navigate to the Parrot tab of your devtools panel.
15 |
16 | ### Standalone
17 |
18 | - Download and unzip the [zipped standalone folder](https://github.com/americanexpress/parrot/blob/main/packages/parrot-devtools/parrot-devtools.zip)
19 | - Open the `index.html` file in any browser.
20 |
--------------------------------------------------------------------------------
/.github/workflows/stale.yml:
--------------------------------------------------------------------------------
1 | name: Mark stale issues and pull requests
2 |
3 | on:
4 | schedule:
5 | - cron: "0 0 * * *"
6 |
7 | jobs:
8 | stale:
9 |
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - uses: actions/stale@v3
14 | with:
15 | repo-token: ${{ secrets.GITHUB_TOKEN }}
16 | stale-issue-message: 'This issue is stale because it has been open 30 days with no activity.'
17 | stale-pr-message: 'This pull request is stale because it has been open 30 days with no activity.'
18 | stale-issue-label: 'stale-issue'
19 | exempt-issue-labels: 'enhancement,documentation,good-first-issue,question'
20 | stale-pr-label: 'stale-pr'
21 | exempt-pr-labels: 'work-in-progress'
22 | days-before-stale: 30
23 | days-before-close: -1
24 |
25 |
--------------------------------------------------------------------------------
/examples/pirate-ship-app/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "parrot-example-client",
3 | "version": "1.0.0",
4 | "private": true,
5 | "dependencies": {
6 | "react": "^16.14.0",
7 | "react-dom": "^16.14.0"
8 | },
9 | "scripts": {
10 | "start": "react-scripts start",
11 | "build": "react-scripts build",
12 | "test": "react-scripts test --env=jsdom",
13 | "eject": "react-scripts eject"
14 | },
15 | "proxy": "http://localhost:3001",
16 | "devDependencies": {
17 | "react-scripts": "^5.0.1"
18 | },
19 | "browserslist": {
20 | "production": [
21 | ">0.2%",
22 | "not dead",
23 | "not op_mini all"
24 | ],
25 | "development": [
26 | "last 1 chrome version",
27 | "last 1 firefox version",
28 | "last 1 safari version"
29 | ]
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/packages/parrot-devtools/src/app/utils/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | export * from './constants';
16 | export * from './createStore';
17 | export * from './localStorage';
18 |
--------------------------------------------------------------------------------
/packages/parrot-devtools/src/app/utils/constants.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | export const DEFAULT_MIDDLEWARE_URL = 'http://localhost:3002';
16 | export const PARROT_STATE = 'PARROT_STATE';
17 |
--------------------------------------------------------------------------------
/packages/parrot-devtools/src/app/components/styled/Logo.jsx:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | import styled from 'styled-components';
16 |
17 | export default styled.img`
18 | height: 42.85px;
19 | `;
20 |
--------------------------------------------------------------------------------
/packages/parrot-devtools/src/app/hooks/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
13 | * or implied. See the License for the specific language governing
14 | * permissions and limitations under the License.
15 | */
16 |
17 | export { default as useDevTools } from './useDevTools';
18 | export { default as useScenarios } from './useScenarios';
19 |
--------------------------------------------------------------------------------
/packages/parrot-devtools/src/app/components/styled/Grid.jsx:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | import styled from 'styled-components';
16 |
17 | export default styled.div`
18 | height: 100%;
19 | display: grid;
20 | `;
21 |
--------------------------------------------------------------------------------
/packages/parrot-devtools/src/app/components/styled/Scrollable.jsx:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | import styled from 'styled-components';
16 |
17 | export default styled.div`
18 | overflow-y: scroll;
19 | height: 100%;
20 | `;
21 |
--------------------------------------------------------------------------------
/packages/parrot-devtools/src/app/components/styled/Content.jsx:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | import styled from 'styled-components';
16 |
17 | export default styled.div`
18 | width: 100%;
19 | height: calc(100% - 97.85px);
20 | `;
21 |
--------------------------------------------------------------------------------
/packages/parrot-devtools/src/browser/extension/devtools.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | // create our devtool tab/panel in the devtools
16 | chrome.devtools.panels.create('Parrot', 'assets/img/parrot_48x.png', 'views/devtool-panel.html');
17 |
--------------------------------------------------------------------------------
/examples/pirate-ship-app/client/src/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | import React from 'react';
16 | import ReactDOM from 'react-dom';
17 | import App from './App';
18 |
19 | ReactDOM.render(, document.getElementById('root'));
20 |
--------------------------------------------------------------------------------
/packages/parrot-devtools/src/app/components/styled/Navigation.jsx:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | import styled from 'styled-components';
16 |
17 | export default styled.div`
18 | bottom: 0;
19 | width: 100%;
20 | height: 62.85px;
21 | `;
22 |
--------------------------------------------------------------------------------
/packages/parrot-devtools/src/app/components/styled/Container.jsx:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | import styled from 'styled-components';
16 |
17 | export default styled.div`
18 | position: absolute;
19 | width: 100%;
20 | height: 100%;
21 | `;
22 |
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
13 | * or implied. See the License for the specific language governing
14 | * permissions and limitations under the License.
15 | */
16 |
17 | module.exports = {
18 | extends: ['@commitlint/config-conventional'],
19 | rules: {
20 | 'scope-case': [2, 'always', ['pascal-case', 'camel-case', 'kebab-case']],
21 | },
22 | };
23 |
--------------------------------------------------------------------------------
/examples/pirate-ship-app/server.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | const express = require('express');
16 | const parrot = require('parrot-middleware');
17 | const scenarios = require('./scenarios');
18 |
19 | const app = express();
20 | app.use(parrot(scenarios));
21 | app.listen(3001);
22 |
--------------------------------------------------------------------------------
/packages/parrot-core/src/utils/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | export { default as normalizeScenarios } from './normalizeScenarios';
16 | export { default as matchMock } from './matchMock';
17 | export { default as resolveResponse } from './resolveResponse';
18 | export { default as logger } from './logger';
19 |
--------------------------------------------------------------------------------
/packages/parrot-devtools/__tests__/app/hooks/index.spec.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
13 | * or implied. See the License for the specific language governing
14 | * permissions and limitations under the License.
15 | */
16 |
17 | import * as hooks from '../../../src/app/hooks';
18 |
19 | describe('hooks', () => {
20 | it('should consistently export hooks', () => {
21 | expect(Object.entries(hooks)).toMatchSnapshot();
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Test
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 | branches:
9 | - main
10 |
11 | jobs:
12 | tests:
13 | runs-on: ubuntu-latest
14 | strategy:
15 | matrix:
16 | node: ['16.x', '18.x' ]
17 | name: Node ${{ matrix.node }}
18 | steps:
19 | - name: Checkout
20 | uses: actions/checkout@v2
21 | with:
22 | fetch-depth: 0
23 | persist-credentials: false
24 | ref: ${{ github.event.pull_request.head.sha }}
25 | - run: |
26 | git remote set-branches --add origin main
27 | git fetch
28 | - name: Node Install
29 | with:
30 | node-version: ${{ matrix.node }}
31 | uses: actions/setup-node@v1
32 | - name: Installing Packages
33 | env:
34 | NODE_ENV: development
35 | run: npm ci
36 | - name: Unit Tests
37 | run: npm run test
38 |
--------------------------------------------------------------------------------
/packages/parrot-devtools/__tests__/app/index.spec.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | import ReactDOM from 'react-dom';
16 | import '../../src/app';
17 |
18 | jest.mock('react-dom');
19 |
20 | describe('Renders DevTools', () => {
21 | it('renders app', () => {
22 | expect(ReactDOM.render).toHaveBeenCalled();
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/packages/parrot-core/src/utils/getParams.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | import { match } from 'path-to-regexp';
16 |
17 | export default function getParams(path, route) {
18 | const matchRoute = match(route);
19 | const result = matchRoute(path);
20 | if (!result) {
21 | return {};
22 | }
23 | return result.params;
24 | }
25 |
--------------------------------------------------------------------------------
/packages/parrot-devtools/src/app/components/styled/ClearButton.jsx:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2020 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | import styled from 'styled-components';
16 |
17 | const ClearButton = styled.span`
18 | margin-left: -20px;
19 | cursor: pointer;
20 | height: 16px;
21 | `;
22 | ClearButton.displayName = 'button';
23 |
24 | export default ClearButton;
25 |
--------------------------------------------------------------------------------
/packages/parrot-core/jest.setup.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
13 | * or implied. See the License for the specific language governing
14 | * permissions and limitations under the License.
15 | */
16 |
17 | const localStorageMock = {
18 | getItem: jest.fn(),
19 | setItem: jest.fn(),
20 | removeItem: jest.fn(),
21 | clear: jest.fn(),
22 | };
23 | global.localStorage = localStorageMock;
24 | // eslint-disable-next-line no-underscore-dangle
25 | global._localStorage = localStorageMock;
26 |
--------------------------------------------------------------------------------
/packages/parrot-devtools/src/app/hooks/useDevTools.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
13 | * or implied. See the License for the specific language governing
14 | * permissions and limitations under the License.
15 | */
16 |
17 | import React from 'react';
18 |
19 | export default function useDevTools() {
20 | const [showSettings, setShowSettings] = React.useState(false);
21 |
22 | return {
23 | showSettings,
24 | toggleSettings: () => setShowSettings(state => !state),
25 | };
26 | }
27 |
--------------------------------------------------------------------------------
/packages/parrot-friendly/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "parrot-friendly",
3 | "version": "5.3.0",
4 | "contributors": [
5 | "Jack Cross ",
6 | "Nathan Force ",
7 | "Jason Schapiro"
8 | ],
9 | "description": "BDD syntax for writing Parrot scenarios.",
10 | "files": [
11 | "lib"
12 | ],
13 | "main": "lib",
14 | "license": "Apache-2.0",
15 | "repository": {
16 | "type": "git",
17 | "url": "https://github.com/americanexpress/parrot.git",
18 | "directory": "packages/parrot-friendly"
19 | },
20 | "scripts": {
21 | "clean": "rimraf lib",
22 | "prebuild": "npm run clean",
23 | "build": "babel src --out-dir lib --copy-files",
24 | "prepublish": "npm run build"
25 | },
26 | "dependencies": {
27 | "parrot-graphql": "^5.3.0"
28 | },
29 | "devDependencies": {
30 | "@babel/cli": "^7.22.10",
31 | "@babel/core": "^7.22.10",
32 | "babel-preset-amex": "^3.6.1",
33 | "rimraf": "^3.0.2"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/packages/parrot-fetch/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "parrot-fetch",
3 | "version": "5.3.0",
4 | "contributors": [
5 | "Jack Cross ",
6 | "Nathan Force ",
7 | "Jason Schapiro"
8 | ],
9 | "description": "A Fetch mocking implementation of Parrot.",
10 | "files": [
11 | "lib"
12 | ],
13 | "main": "lib",
14 | "license": "Apache-2.0",
15 | "repository": {
16 | "type": "git",
17 | "url": "https://github.com/americanexpress/parrot.git",
18 | "directory": "packages/parrot-fetch"
19 | },
20 | "scripts": {
21 | "clean": "rimraf lib",
22 | "prebuild": "npm run clean",
23 | "build": "babel src --out-dir lib --copy-files",
24 | "prepublish": "npm run build"
25 | },
26 | "dependencies": {
27 | "parrot-core": "^5.3.0",
28 | "url-parse": "^1.5.10"
29 | },
30 | "devDependencies": {
31 | "@babel/cli": "^7.22.10",
32 | "@babel/core": "^7.22.10",
33 | "babel-preset-amex": "^3.6.1",
34 | "rimraf": "^3.0.2"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/packages/parrot-devtools/src/app/components/styled/SearchBar.jsx:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2020 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | import styled from 'styled-components';
16 |
17 | const SearchBar = styled.input`
18 | border-radius: 3px;
19 | outline: none;
20 | border: 1px solid #2874b7;
21 | padding: 0px 0px 0px 5px;
22 | height: 25px;
23 | width: 50%;
24 | `;
25 | SearchBar.displayName = 'input';
26 |
27 | export default SearchBar;
28 |
--------------------------------------------------------------------------------
/packages/parrot-devtools/__tests__/app/components/__snapshots__/ScenariosDisplay.spec.jsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`ScenariosDisplay ScenariosDisplay should render ScenariosDisplay component 1`] = `
4 |
5 |
18 |
19 |
32 |
33 |
34 | `;
35 |
--------------------------------------------------------------------------------
/packages/parrot-devtools/scripts/pack-base.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | /* eslint import/no-extraneous-dependencies: 'off' */
16 | const fs = require('fs-extra');
17 | const zipAndRm = require('./zip-and-rm');
18 |
19 | fs.removeSync('./parrot-devtools');
20 | fs.ensureDirSync('./parrot-devtools');
21 | fs.copySync('./dist/base', './parrot-devtools');
22 | fs.copySync('./src/browser/views/base', './parrot-devtools');
23 | zipAndRm('./parrot-devtools', './parrot-devtools.zip');
24 |
--------------------------------------------------------------------------------
/packages/parrot-devtools/src/browser/extension/background.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | // This is necessary because the chrome.tab API is not exposed to the dev panel.
16 | chrome.runtime.onMessage.addListener(({ tabId }, sender, sendResponse) => {
17 | chrome.tabs.get(tabId, ({ url }) => sendResponse({ url }));
18 |
19 | // returning true tells chrome to keep the message port open until
20 | // the async chrome.tabs above returns and sendResponse is called
21 | return true;
22 | });
23 |
--------------------------------------------------------------------------------
/packages/parrot-middleware/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "parrot-middleware",
3 | "version": "5.3.0",
4 | "contributors": [
5 | "Jack Cross ",
6 | "Nathan Force ",
7 | "Jason Schapiro"
8 | ],
9 | "description": "An Express middleware implementation of Parrot.",
10 | "files": [
11 | "lib"
12 | ],
13 | "main": "lib",
14 | "license": "Apache-2.0",
15 | "repository": {
16 | "type": "git",
17 | "url": "https://github.com/americanexpress/parrot.git",
18 | "directory": "packages/parrot-middleware"
19 | },
20 | "scripts": {
21 | "clean": "rimraf lib",
22 | "prebuild": "npm run clean",
23 | "build": "rollup -c",
24 | "prepublish": "npm run build"
25 | },
26 | "dependencies": {
27 | "body-parser": "^1.20.2",
28 | "cookie-parser": "^1.4.7",
29 | "express": "^4.18.2",
30 | "parrot-core": "^5.3.0"
31 | },
32 | "devDependencies": {
33 | "@rollup/plugin-babel": "^5.3.1",
34 | "@rollup/plugin-node-resolve": "^8.4.0",
35 | "babel-preset-amex": "^3.6.1",
36 | "rimraf": "^3.0.2",
37 | "rollup": "^2.79.1"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/packages/parrot-devtools/src/app/components/styled/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | export { default as Container } from './Container';
16 | export { default as Content } from './Content';
17 | export { default as Grid } from './Grid';
18 | export { default as Logo } from './Logo';
19 | export { default as Navigation } from './Navigation';
20 | export { default as Scrollable } from './Scrollable';
21 | export { default as SearchBar } from './SearchBar';
22 | export { default as ClearButton } from './ClearButton';
23 |
--------------------------------------------------------------------------------
/packages/parrot-devtools/src/app/utils/createStore.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | /* eslint import/prefer-default-export: 'off' */
16 | import { createStore } from 'redux';
17 | import reducer from '../ducks';
18 | import { getLocalStorage, setLocalStorage } from './localStorage';
19 |
20 | export function createLocalStorageStore() {
21 | const state = getLocalStorage();
22 | const store = createStore(reducer, state);
23 | store.subscribe(() => setLocalStorage(store.getState()));
24 | return store;
25 | }
26 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
13 | * or implied. See the License for the specific language governing
14 | * permissions and limitations under the License.
15 | */
16 |
17 | module.exports = {
18 | preset: 'amex-jest-preset-react',
19 | collectCoverageFrom: ['packages/*/src/**/*.{js,jsx}'],
20 | moduleNameMapper: {
21 | '\\.png': 'identity-obj-proxy',
22 | react$: '/node_modules/react',
23 | 'react-dom$': '/node_modules/react-dom',
24 | 'react-test-renderer$': '/node_modules/react-test-renderer',
25 | },
26 | setupFiles: ['./jest.setup.js', './packages/parrot-core/jest.setup.js'],
27 | };
28 |
--------------------------------------------------------------------------------
/lockFileLint.js:
--------------------------------------------------------------------------------
1 | const { spawnSync } = require('child_process');
2 | // eslint-disable-next-line import/no-extraneous-dependencies
3 | const glob = require('glob');
4 |
5 | const lockfileGlob = '**/package-lock.json';
6 | const ignoreGlob = '**/node_modules/**/package-lock.json';
7 |
8 | const invalidations = [];
9 |
10 | glob(lockfileGlob, { ignore: ignoreGlob }, (globError, lockFiles) => {
11 | if (globError) {
12 | process.stderr.write(globError);
13 | // eslint-disable-next-line unicorn/no-process-exit
14 | process.exit(1);
15 | }
16 |
17 | lockFiles.forEach(lockPath => {
18 | const { stderr } = spawnSync('./node_modules/.bin/lockfile-lint', [
19 | '-p',
20 | lockPath,
21 | '-t',
22 | 'npm',
23 | '-a',
24 | 'npm',
25 | '-o',
26 | 'https:',
27 | '-c',
28 | '-i',
29 | ]);
30 | const error = stderr.toString();
31 | if (error) invalidations.push([lockPath, error].join(':\n\n'));
32 | });
33 |
34 | if (invalidations.length > 0) {
35 | process.stderr.write(invalidations.join('\n'));
36 | // eslint-disable-next-line unicorn/no-process-exit
37 | process.exit(1);
38 | }
39 | });
40 |
--------------------------------------------------------------------------------
/packages/parrot-devtools/scripts/zip-and-rm.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | /* eslint import/no-extraneous-dependencies: 'off' */
16 | const fs = require('fs-extra');
17 | const archiver = require('archiver');
18 |
19 | module.exports = (directory, zipFile) => {
20 | const output = fs.createWriteStream(zipFile);
21 | const archive = archiver('zip');
22 |
23 | output.on('close', () => {
24 | fs.removeSync(directory);
25 | });
26 |
27 | archive.pipe(output);
28 | archive.directory(directory, false);
29 | archive.finalize();
30 | };
31 |
--------------------------------------------------------------------------------
/packages/parrot-devtools/__tests__/browser/extension/devtools.spec.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | global.chrome = {
16 | devtools: {
17 | panels: {
18 | create: jest.fn(),
19 | },
20 | },
21 | };
22 |
23 | require('../../../src/browser/extension/devtools');
24 |
25 | describe('devtools', () => {
26 | it('should setup tab', () => {
27 | expect(global.chrome.devtools.panels.create).toHaveBeenCalledWith(
28 | 'Parrot',
29 | 'assets/img/parrot_48x.png',
30 | 'views/devtool-panel.html'
31 | );
32 | });
33 | });
34 |
--------------------------------------------------------------------------------
/packages/parrot-core/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "parrot-core",
3 | "version": "5.3.0",
4 | "contributors": [
5 | "Jack Cross ",
6 | "Nathan Force ",
7 | "Jason Schapiro"
8 | ],
9 | "description": "Common Parrot functionality.",
10 | "files": [
11 | "lib"
12 | ],
13 | "main": "lib",
14 | "license": "Apache-2.0",
15 | "repository": {
16 | "type": "git",
17 | "url": "https://github.com/americanexpress/parrot.git",
18 | "directory": "packages/parrot-core"
19 | },
20 | "scripts": {
21 | "clean": "rimraf lib",
22 | "prebuild": "npm run clean",
23 | "build": "babel src --out-dir lib --copy-files",
24 | "prepublish": "npm run build"
25 | },
26 | "dependencies": {
27 | "@babel/runtime": "^7.22.10",
28 | "chalk": "^1.1.3",
29 | "lodash": "^4.17.21",
30 | "path-to-regexp": "^8.2.0",
31 | "util-inspect": "^0.1.8"
32 | },
33 | "devDependencies": {
34 | "@babel/cli": "^7.22.10",
35 | "@babel/core": "^7.22.10",
36 | "@babel/plugin-transform-runtime": "^7.22.10",
37 | "babel-preset-amex": "^3.6.1",
38 | "jest-when": "^3.6.0",
39 | "rimraf": "^3.0.2"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/packages/parrot-devtools/src/app/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | import 'whatwg-fetch';
16 | import React from 'react';
17 | import ReactDOM from 'react-dom';
18 | import { Provider } from 'react-redux';
19 | import DevTools from './components/DevTools';
20 | import { createLocalStorageStore } from './utils';
21 |
22 | const store = createLocalStorageStore();
23 |
24 | ReactDOM.render(
25 | // eslint-disable-next-line react/jsx-filename-extension
26 |
27 |
28 | ,
29 | document.querySelector('root')
30 | );
31 |
--------------------------------------------------------------------------------
/packages/parrot-server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "parrot-server",
3 | "version": "5.3.0",
4 | "contributors": [
5 | "Andres Escobar ",
6 | "Jack Cross ",
7 | "Nathan Force ",
8 | "Jason Schapiro"
9 | ],
10 | "description": "CLI to get a parrot server up and running",
11 | "files": [
12 | "lib"
13 | ],
14 | "license": "Apache-2.0",
15 | "repository": {
16 | "type": "git",
17 | "url": "https://github.com/americanexpress/parrot.git",
18 | "directory": "packages/parrot-server"
19 | },
20 | "bin": {
21 | "parrot-server": "./lib/index.js"
22 | },
23 | "scripts": {
24 | "clean": "rimraf lib",
25 | "prebuild": "npm run clean",
26 | "build": "babel bin --out-dir lib --copy-files",
27 | "prepublish": "npm run build"
28 | },
29 | "dependencies": {
30 | "express": "^4.18.2",
31 | "parrot-middleware": "^5.3.0",
32 | "yargs": "^11.1.1"
33 | },
34 | "devDependencies": {
35 | "@babel/cli": "^7.22.10",
36 | "@babel/core": "^7.22.10",
37 | "node-fetch": "^2.6.12",
38 | "pretty-format": "^22.4.3",
39 | "rimraf": "^3.0.2",
40 | "supertest": "^3.4.2",
41 | "wait-port": "^0.2.14"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/packages/parrot-devtools/__tests__/app/utils/createStore.spec.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | import { createLocalStorageStore, getLocalStorage, setLocalStorage } from '../../../src/app/utils';
16 |
17 | jest.mock('../../../src/app/utils/localStorage');
18 | jest.mock('redux', () => ({
19 | createStore: () => ({ subscribe: fn => fn(), getState: () => null }),
20 | }));
21 |
22 | describe('createStore', () => {
23 | it('reads and writes localStorage', () => {
24 | createLocalStorageStore();
25 | expect(getLocalStorage).toHaveBeenCalled();
26 | expect(setLocalStorage).toHaveBeenCalled();
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/packages/parrot-devtools/src/app/ducks/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | import { DEFAULT_MIDDLEWARE_URL } from '../utils/constants';
16 |
17 | export const SET_URL = 'SET_URL';
18 |
19 | const initialState = {
20 | url: DEFAULT_MIDDLEWARE_URL,
21 | };
22 |
23 | export default function reducer(state = initialState, { type, url }) {
24 | switch (type) {
25 | case SET_URL: {
26 | return {
27 | ...state,
28 | url,
29 | };
30 | }
31 | default: {
32 | return state;
33 | }
34 | }
35 | }
36 |
37 | export const setUrl = url => ({
38 | type: SET_URL,
39 | url,
40 | });
41 |
--------------------------------------------------------------------------------
/packages/parrot-server/bin/utils/createServer.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | const path = require('path');
16 | const express = require('express');
17 | const parrot = require('parrot-middleware');
18 |
19 | const createServer = pathToScenarios => {
20 | const absolutePathToScenarios = path.isAbsolute(pathToScenarios)
21 | ? pathToScenarios
22 | : path.resolve(pathToScenarios);
23 | const app = express();
24 |
25 | // eslint-disable-next-line global-require, import/no-dynamic-require
26 | app.use(parrot(require(absolutePathToScenarios)));
27 |
28 | return app;
29 | };
30 |
31 | module.exports = createServer;
32 |
--------------------------------------------------------------------------------
/packages/parrot-devtools/src/app/utils/localStorage.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | import { PARROT_STATE } from './constants';
16 |
17 | export const getLocalStorage = () => {
18 | try {
19 | const serialized = localStorage.getItem(PARROT_STATE);
20 | if (serialized === null) {
21 | return undefined;
22 | }
23 | return JSON.parse(serialized);
24 | } catch (e) {
25 | return undefined;
26 | }
27 | };
28 |
29 | export const setLocalStorage = value => {
30 | try {
31 | const serialized = JSON.stringify(value);
32 | localStorage.setItem(PARROT_STATE, serialized);
33 | } catch (e) {
34 | // do nothing
35 | }
36 | };
37 |
--------------------------------------------------------------------------------
/packages/parrot-graphql/src/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | const { mockServer } = require('@graphql-tools/mock');
16 |
17 | // eslint-disable-next-line max-params
18 | module.exports = function graphql(path, schema, mocks, preserveResolvers = true) {
19 | const server = mockServer(schema, mocks, preserveResolvers);
20 | return {
21 | request: ({ method }, match) => match({ path }) && (method === 'GET' || method === 'POST'),
22 | response: {
23 | body: ({ method, query: queryString, body }) => {
24 | const { query, variables } = method === 'GET' ? queryString : body;
25 | return server.query(query, variables);
26 | },
27 | },
28 | };
29 | };
30 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Manually Publish
2 |
3 | on: workflow_dispatch
4 | jobs:
5 | info:
6 | name: Check commit
7 | runs-on: ubuntu-latest
8 | if: "!contains(github.event.head_commit.message, '[skip ci]')"
9 | steps:
10 | - id: commit
11 | run: echo "message=${{ github.event.head_commit.message }}" >> $GITHUB_OUTPUT
12 | outputs:
13 | commitMsg: ${{ steps.commit.outputs.message }}
14 | publish:
15 | name: Publish
16 | needs: [info]
17 | runs-on: ubuntu-latest
18 | steps:
19 | - name: Checkout
20 | uses: actions/checkout@v2
21 | with:
22 | fetch-depth: 0
23 | persist-credentials: false
24 | ref: main
25 | - name: Node Install
26 | uses: actions/setup-node@v1
27 | with:
28 | node-version: '18'
29 | - name: Installing Packages
30 | env:
31 | NODE_ENV: development
32 | run: npm ci
33 | - name: Testing Packages
34 | run: npm test
35 | - name: Releasing Packages
36 | env:
37 | NODE_ENV: production
38 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
39 | GITHUB_TOKEN: ${{ secrets.PA_TOKEN }}
40 | run: |-
41 | echo "//registry.npmjs.org/:_authToken=\${NPM_TOKEN}" >> $HOME/.npmrc 2> /dev/null
42 | npm run lerna:publish
43 |
--------------------------------------------------------------------------------
/packages/parrot-devtools/__tests__/browser/extension/background.spec.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | const sendResponse = jest.fn();
16 |
17 | global.chrome = {
18 | runtime: {
19 | onMessage: {
20 | addListener: jest.fn(cb => cb({}, null, sendResponse)),
21 | },
22 | },
23 | tabs: {
24 | get: jest.fn((tabId, cb) => cb({})),
25 | },
26 | };
27 |
28 | require('../../../src/browser/extension/background');
29 |
30 | describe('devtools', () => {
31 | it('should setup tab', () => {
32 | expect(global.chrome.runtime.onMessage.addListener).toHaveBeenCalled();
33 | expect(global.chrome.tabs.get).toHaveBeenCalled();
34 | expect(sendResponse).toHaveBeenCalled();
35 | });
36 | });
37 |
--------------------------------------------------------------------------------
/packages/parrot-devtools/scripts/pack-extension.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | /* eslint import/no-extraneous-dependencies: 'off' */
16 | const fs = require('fs-extra');
17 | const zipAndRm = require('./zip-and-rm');
18 |
19 | fs.removeSync('./parrot-devtools-extension');
20 | fs.ensureDirSync('./parrot-devtools-extension');
21 | fs.copySync('./dist/extension', './parrot-devtools-extension');
22 | fs.copySync('./src/browser/views/extension', './parrot-devtools-extension/views');
23 | fs.copySync('./src/assets', './parrot-devtools-extension/assets');
24 | fs.copySync('./src/browser/extension/manifest.json', './parrot-devtools-extension/manifest.json');
25 | zipAndRm('./parrot-devtools-extension', './parrot-devtools-extension.zip');
26 |
--------------------------------------------------------------------------------
/packages/parrot-fetch/__tests__/index.spec.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | import parrotFetch from '../src';
16 |
17 | describe('parrot-fetch', () => {
18 | it('should mock fetch', () => {
19 | const contextFetch = fetch;
20 | parrotFetch();
21 | expect(fetch).not.toBe(contextFetch);
22 | });
23 | it('should mock fetchClient', () => {
24 | const fetchWrapper = {
25 | fetchClient: fetch,
26 | };
27 | const scenarios = {
28 | one: [
29 | {
30 | request: () => 'test',
31 | response: () => 'response',
32 | },
33 | ],
34 | };
35 | parrotFetch(scenarios, fetchWrapper);
36 | expect(fetch).not.toBe(fetchWrapper.fetchClient);
37 | });
38 | });
39 |
--------------------------------------------------------------------------------
/packages/parrot-friendly/src/Mock.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | export default class Mock {
16 | constructor(structure) {
17 | this.structure = structure;
18 | }
19 |
20 | query = value => {
21 | this.structure.request.query = value;
22 | return this;
23 | };
24 |
25 | headers = value => {
26 | this.structure.request.headers = value;
27 | return this;
28 | };
29 |
30 | response = value => {
31 | this.structure.response.body = value;
32 | return this;
33 | };
34 |
35 | delay = value => {
36 | this.structure.response.delay = value;
37 | return this;
38 | };
39 |
40 | status = value => {
41 | this.structure.response.status = value;
42 | return this;
43 | };
44 | }
45 |
--------------------------------------------------------------------------------
/lernaDeploy.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,either express
11 | * or implied. See the License for the specific language governing permissions and limitations
12 | * under the License.
13 | */
14 |
15 | const { exec } = require('child_process');
16 | // This regex was obtained from https://semver.org/ test it out here https://regex101.com/r/vkijKf/1/
17 | const regex = /^chore\(release\): (0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/gm;
18 | const commitMessage = process.argv[2]; // This is the commit message
19 | if (commitMessage != null && regex.test(commitMessage)) {
20 | exec('npm run lerna:publish').stderr.pipe(process.stderr);
21 | } else {
22 | // eslint-disable-next-line no-console
23 | console.log('Lerna Deploy: No valid release commit detected');
24 | }
25 |
--------------------------------------------------------------------------------
/packages/parrot-devtools/__tests__/browser/extension/devtool-panel.spec.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | global.chrome = {
16 | runtime: {
17 | sendMessage: jest.fn((obj, fn) => fn()),
18 | },
19 | devtools: {
20 | inspectedWindow: {
21 | tabId: 'squawk',
22 | },
23 | },
24 | };
25 |
26 | const ReactDOM = require('react-dom');
27 | require('../../../src/browser/extension/devtool-panel');
28 |
29 | jest.mock('react-dom');
30 |
31 | describe('Renders DevTools', () => {
32 | it('renders app', () => {
33 | expect(ReactDOM.render).toHaveBeenCalled();
34 | expect(global.chrome.runtime.sendMessage).toHaveBeenCalledWith(
35 | { tabId: 'squawk' },
36 | expect.any(Function)
37 | );
38 | });
39 | });
40 |
--------------------------------------------------------------------------------
/packages/parrot-devtools/__tests__/app/ducks/index.spec.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | import reducer, { setUrl, SET_URL } from '../../../src/app/ducks';
16 | import { DEFAULT_MIDDLEWARE_URL } from '../../../src/app/utils';
17 |
18 | describe('ducks', () => {
19 | it('should create SET_URL action', () => {
20 | expect(setUrl('squawk')).toMatchObject({
21 | type: SET_URL,
22 | url: 'squawk',
23 | });
24 | });
25 |
26 | it('should return state with URL', () => {
27 | expect(reducer({}, { type: SET_URL, url: 'squawk' })).toMatchObject({ url: 'squawk' });
28 | });
29 |
30 | it('should return state untouched', () => {
31 | expect(reducer(undefined, {})).toMatchObject({ url: DEFAULT_MIDDLEWARE_URL });
32 | });
33 | });
34 |
--------------------------------------------------------------------------------
/packages/parrot-devtools/src/browser/extension/devtool-panel.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | // eslint-disable-next-line import/no-extraneous-dependencies
16 | import 'whatwg-fetch';
17 | import React from 'react';
18 | import ReactDOM from 'react-dom';
19 | import { Provider } from 'react-redux';
20 | import DevTools from '../../app/components/DevTools';
21 | import { createLocalStorageStore } from '../../app/utils';
22 |
23 | const store = createLocalStorageStore();
24 |
25 | chrome.runtime.sendMessage({ tabId: chrome.devtools.inspectedWindow.tabId }, () => {
26 | ReactDOM.render(
27 | // eslint-disable-next-line react/jsx-filename-extension
28 |
29 |
30 | ,
31 | document.querySelector('root')
32 | );
33 | });
34 |
--------------------------------------------------------------------------------
/packages/parrot-devtools/__tests__/app/components/__snapshots__/DevTools.spec.jsx.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`DevTools component hides settings when false 1`] = `
4 |
7 |
10 |
11 |
12 |
15 |
18 |
21 |
26 |
27 |
28 | `;
29 |
30 | exports[`DevTools component renders selector when true 1`] = `
31 |
34 |
37 |
38 |
39 |
42 |
45 |
48 |
53 |
54 |
55 | `;
56 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | jobs:
9 | prepare:
10 | name: Prepare
11 | runs-on: ubuntu-latest
12 | if: "!contains(github.event.head_commit.message, '[skip ci]')"
13 | steps:
14 | - id: commit
15 | run: echo "message=${{ github.event.head_commit.message }}" >> $GITHUB_OUTPUT
16 | outputs:
17 | commitMsg: ${{ steps.commit.outputs.message }}
18 | release:
19 | name: Release
20 | needs: prepare
21 | runs-on: ubuntu-latest
22 | steps:
23 | - name: Checkout
24 | uses: actions/checkout@v2
25 | with:
26 | persist-credentials: false
27 | - name: Setup Node.js
28 | uses: actions/setup-node@v1
29 | with:
30 | node-version: 18
31 | - name: Install dependencies
32 | run: npm ci
33 | - name: Run Tests
34 | run: npm test
35 | - name: Release
36 | env:
37 | GIT_AUTHOR_EMAIL: ${{ secrets.GIT_AUTHOR_EMAIL }}
38 | GIT_AUTHOR_NAME: ${{ secrets.GIT_AUTHOR_NAME }}
39 | GIT_COMMITTER_EMAIL: ${{ secrets.GIT_COMMITTER_EMAIL }}
40 | GIT_COMMITTER_NAME: ${{ secrets.GIT_COMMITTER_NAME }}
41 | GITHUB_TOKEN: ${{ secrets.PA_TOKEN }}
42 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
43 | run: |-
44 | echo "//registry.npmjs.org/:_authToken=\${NPM_TOKEN}" >> $HOME/.npmrc 2> /dev/null
45 | npm run lerna:deploy -- "${{ needs.prepare.outputs.commitMsg }}"
--------------------------------------------------------------------------------
/examples/pirate-ship-app/scenarios.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | const scenarios = {
16 | 'has one ship': [
17 | {
18 | request: '/ship_log',
19 | response: {
20 | body: [{ name: 'The Jolly Roger', captain: 'Captain Hook' }],
21 | },
22 | },
23 | ],
24 | 'has more ships': [
25 | {
26 | request: '/ship_log',
27 | response: {
28 | body: [
29 | { name: 'The Jolly Roger', captain: 'Captain Hook' },
30 | { name: 'The Black Pearl', captain: 'Jack Sparrow' },
31 | { name: 'Flying Dutchman', captain: 'Davy Jones' },
32 | { name: 'The Wanderer', captain: 'Captain Ron' },
33 | ],
34 | },
35 | },
36 | ],
37 | 'has a server error': [
38 | {
39 | request: '/ship_log',
40 | response: {
41 | status: 500,
42 | },
43 | },
44 | ],
45 | };
46 |
47 | module.exports = scenarios;
48 |
--------------------------------------------------------------------------------
/packages/parrot-devtools/webpack.config.babel.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
13 | * or implied. See the License for the specific language governing
14 | * permissions and limitations under the License.
15 | */
16 |
17 | const path = require('path');
18 |
19 | module.exports = {
20 | entry: {
21 | 'extension/devtool-panel': ['./src/browser/extension/devtool-panel.js'],
22 | 'extension/devtools': ['./src/browser/extension/devtools.js'],
23 | 'extension/background': ['./src/browser/extension/background.js'],
24 | 'base/devtools': ['./src/app/index.js'],
25 | },
26 | output: {
27 | path: path.join(__dirname, '/dist'),
28 | filename: '[name].bundle.js',
29 | },
30 | resolve: {
31 | extensions: ['.js', '.jsx'],
32 | },
33 | module: {
34 | rules: [
35 | {
36 | test: /\.jsx?$/,
37 | loader: 'babel-loader',
38 | exclude: /node_modules/,
39 | },
40 | {
41 | test: /\.png$/,
42 | loader: 'url-loader?mimetype=image/png',
43 | },
44 | ],
45 | },
46 | };
47 |
--------------------------------------------------------------------------------
/packages/parrot-server/__fixtures__/scenarios.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | const scenarios = {
16 | 'has one ship': [
17 | {
18 | request: '/ship_log',
19 | response: {
20 | body: [{ name: 'The Jolly Roger', captain: 'Captain Hook' }],
21 | },
22 | },
23 | ],
24 | 'has more ships': [
25 | {
26 | request: '/ship_log',
27 | response: {
28 | body: [
29 | { name: 'The Jolly Roger', captain: 'Captain Hook' },
30 | { name: 'The Black Pearl', captain: 'Jack Sparrow' },
31 | { name: 'Flying Dutchman', captain: 'Davy Jones' },
32 | { name: 'The Wanderer', captain: 'Captain Ron' },
33 | ],
34 | },
35 | },
36 | ],
37 | 'has a server error': [
38 | {
39 | request: '/ship_log',
40 | response: {
41 | status: 500,
42 | },
43 | },
44 | ],
45 | };
46 |
47 | module.exports = scenarios;
48 |
--------------------------------------------------------------------------------
/packages/parrot-fetch/src/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | import ParrotFetch from './ParrotFetch';
16 |
17 | /* istanbul ignore next */
18 | const context = typeof global === 'undefined' ? self : global; // eslint-disable-line no-restricted-globals
19 | const contextFetch = context.fetch.bind(context);
20 | export const PARROT_STATE = 'PARROT_STATE';
21 |
22 | export default function init(scenarios, fetchWrapperParam) {
23 | const fetchWrapper = fetchWrapperParam;
24 | let parrotFetch;
25 | // option to mock a fetchClient that is different from the global fetch by passing the fetchClient in through a fetchWrapper object.
26 | if (fetchWrapper) {
27 | parrotFetch = new ParrotFetch(scenarios, contextFetch);
28 | fetchWrapper.fetchClient = parrotFetch.resolve;
29 | } else {
30 | parrotFetch = new ParrotFetch(scenarios, contextFetch);
31 | context.fetch = parrotFetch.resolve;
32 | }
33 | return parrotFetch;
34 | }
35 |
--------------------------------------------------------------------------------
/packages/parrot-middleware/src/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | import { Router } from 'express';
16 | import bodyParser from 'body-parser';
17 | import cookieParser from 'cookie-parser';
18 | import ParrotMiddleware from './ParrotMiddleware';
19 |
20 | export default function parrot(scenarios) {
21 | const parrotMiddleware = new ParrotMiddleware(scenarios);
22 |
23 | const jsonParser = bodyParser.json();
24 | const parrotRouter = Router();
25 |
26 | parrotRouter.post('/parrot/scenario', (req, res) => {
27 | parrotMiddleware.setActiveScenario(req.body.scenario);
28 | res.sendStatus(200);
29 | });
30 |
31 | parrotRouter.get('/parrot/scenario', (req, res) => {
32 | res.json(parrotMiddleware.getActiveScenario());
33 | });
34 |
35 | parrotRouter.get('/parrot/scenarios', (req, res) => {
36 | res.json(parrotMiddleware.getScenarios());
37 | });
38 |
39 | return [jsonParser, cookieParser(), parrotRouter, parrotMiddleware.resolve];
40 | }
41 |
--------------------------------------------------------------------------------
/packages/parrot-devtools/__tests__/app/hooks/useDevTools.spec.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
13 | * or implied. See the License for the specific language governing
14 | * permissions and limitations under the License.
15 | */
16 |
17 | import { renderHook, act } from '@testing-library/react-hooks';
18 |
19 | import useDevTools from '../../../src/app/hooks/useDevTools';
20 |
21 | describe('useDevTools', () => {
22 | it('should return default state and handler', () => {
23 | expect.assertions(1);
24 |
25 | const { result } = renderHook(() => useDevTools());
26 |
27 | expect(result.current).toMatchObject({
28 | showSettings: expect.any(Boolean),
29 | toggleSettings: expect.any(Function),
30 | });
31 | });
32 |
33 | it('should toggle showSettings', () => {
34 | expect.assertions(2);
35 |
36 | const { result } = renderHook(() => useDevTools());
37 |
38 | expect(result.current.showSettings).toEqual(false);
39 |
40 | act(() => {
41 | result.current.toggleSettings();
42 | });
43 |
44 | expect(result.current.showSettings).toEqual(true);
45 | });
46 | });
47 |
--------------------------------------------------------------------------------
/packages/parrot-devtools/src/app/components/ScenariosDisplay.jsx:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
13 | * or implied. See the License for the specific language governing
14 | * permissions and limitations under the License.
15 | */
16 |
17 | import React, { Fragment } from 'react';
18 | import PropTypes from 'prop-types';
19 | import { connect } from 'react-redux';
20 | import { Content } from './styled';
21 | import Scenarios from './Scenarios';
22 | import Toolbar from './Toolbar';
23 | import { useScenarios } from '../hooks';
24 |
25 | function ScenariosDisplay({ url }) {
26 | const scenariosData = useScenarios(url);
27 | return (
28 |
29 |
30 |
31 |
32 |
33 |
34 | );
35 | }
36 |
37 | ScenariosDisplay.propTypes = {
38 | url: PropTypes.string.isRequired,
39 | };
40 |
41 | export const mapStateToProps = ({ url }) => ({ url });
42 |
43 | export const ComponentUnderTest = ScenariosDisplay;
44 | export default connect(mapStateToProps)(ScenariosDisplay);
45 |
--------------------------------------------------------------------------------
/packages/parrot-middleware/src/ParrotMiddleware.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | import Parrot from 'parrot-core';
16 |
17 | class ParrotMiddleware extends Parrot {
18 | normalizeRequest = req => req;
19 |
20 | resolver = (req, res, next) => response => {
21 | if (res.headersSent) {
22 | return;
23 | }
24 | if (!response) {
25 | this.logger.warn('No matching mock found for request', req.path);
26 | next();
27 | return;
28 | }
29 |
30 | const { body, contentType, status } = response;
31 | res.status(status);
32 |
33 | if (contentType) {
34 | res.type(contentType);
35 | }
36 |
37 | if (typeof body === 'object') {
38 | res.json(body);
39 | } else if (typeof body === 'undefined') {
40 | res.sendStatus(status);
41 | } else {
42 | res.send(body);
43 | }
44 | };
45 |
46 | getActiveScenarioOverride = req => (req.cookies ? req.cookies.parrotScenarioOverride : undefined);
47 | }
48 |
49 | export default ParrotMiddleware;
50 |
--------------------------------------------------------------------------------
/packages/parrot-graphql/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | parrot-graphql is a helper library that resolves GET and POST requests to your GraphQL endpoint using mocking logic that is defined for each schema type. Under the hood, parrot-graphql is a small wrapper around [@graphql-tools/mock](https://github.com/apollographql/graphql-tools) `mockServer`.
6 |
7 | For in-depth examples on how to write mock resolvers, read the above documentation from graphql-tools, but below is a simple example to start you on your way.
8 |
9 | ## Example
10 |
11 | ```js
12 | // scenarios.js
13 | const graphql = require('parrot-graphql');
14 | const casual = require('casual'); // A nice mocking tool
15 |
16 | const schema = `
17 | type Query {
18 | shiplog: [ShipLog]
19 | }
20 |
21 | type ShipLog {
22 | Name: String!
23 | Captain: String!
24 | }
25 | `;
26 |
27 | const scenarios = {
28 | 'has a ship log from GraphQL': [
29 | graphql('/graphql', schema, {
30 | Name: () => 'Jolly Roger',
31 | Captain: () => casual.name,
32 | }),
33 | ],
34 | };
35 |
36 | module.exports = scenarios;
37 | ```
38 |
39 | ## API
40 |
41 | ### `graphql(path, schema, mocks)`
42 |
43 | Creates a function for your GraphQL endpoint.
44 |
45 | #### Arguments
46 |
47 | - `path` (_String_): Path of your GraphQL endpoint.
48 | - `schema` (_String_): GraphQL schema string.
49 | - `mocks` (_Object_): Object describing your [mocking logic](https://www.apollographql.com/docs/graphql-tools/mocking.html#Customizing-mocks) that is passed to [graphql-tools](https://github.com/apollographql/graphql-tools) `mockServer`.
50 |
--------------------------------------------------------------------------------
/packages/parrot-devtools/src/app/components/Toolbar.jsx:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
13 | * or implied. See the License for the specific language governing
14 | * permissions and limitations under the License.
15 | */
16 |
17 | import React from 'react';
18 | import PropTypes from 'prop-types';
19 | import { SearchBar, ClearButton } from './styled';
20 |
21 | const Toolbar = ({ scenariosData }) => {
22 | const { filterValue, setFilterValue } = scenariosData;
23 | const handleFilterChange = e => {
24 | scenariosData.setFilterValue(e.target.value.toLowerCase());
25 | };
26 | return (
27 |
28 |
33 | setFilterValue('')} />
34 |
35 | );
36 | };
37 |
38 | export default Toolbar;
39 |
40 | Toolbar.propTypes = {
41 | scenariosData: PropTypes.shape({
42 | filterValue: PropTypes.string,
43 | setFilterValue: PropTypes.func,
44 | }).isRequired,
45 | };
46 |
--------------------------------------------------------------------------------
/packages/parrot-graphql/__tests__/index.spec.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | import graphql from '../src';
16 |
17 | jest.mock('@graphql-tools/mock', () => ({
18 | mockServer: () => ({
19 | query: query => query,
20 | }),
21 | }));
22 |
23 | describe('parrot-graphql', () => {
24 | it('matches a GET request', () => {
25 | const match = jest.fn(() => true);
26 | const { request } = graphql();
27 | expect(request({ method: 'GET' }, match)).toBe(true);
28 | });
29 |
30 | it('matches a POST request', () => {
31 | const match = jest.fn(() => true);
32 | const { request } = graphql();
33 | expect(request({ method: 'POST' }, match)).toBe(true);
34 | });
35 |
36 | it('gets query from query string', () => {
37 | const { body } = graphql().response;
38 | expect(body({ method: 'GET', query: { query: 'squawk' } })).toBe('squawk');
39 | });
40 |
41 | it('gets query from body', () => {
42 | const { body } = graphql().response;
43 | expect(body({ method: 'POST', body: { query: 'squawk' } })).toBe('squawk');
44 | });
45 | });
46 |
--------------------------------------------------------------------------------
/.github/workflows/deploy-parrot-devtools-to-webstore.yml:
--------------------------------------------------------------------------------
1 | name: Deploy parrot-devtools to Chrome Webstore
2 | on:
3 | pull_request:
4 | # only trigger on pull request closed events
5 | types: [ closed ]
6 | # only for PRs to the main branch
7 | branches:
8 | - main
9 | # and only if the extension has been updated
10 | paths:
11 | - 'packages/parrot-devtools/src/browser/extension/**'
12 | - 'packages/parrot-devtools/src/browser/views/extension/**'
13 | jobs:
14 | merge_job:
15 | # this job will only run if the PR has been merged
16 | if: github.event.pull_request.merged == true
17 | runs-on: ubuntu-latest
18 | steps:
19 | - name: Checkout the codebase
20 | uses: actions/checkout@v2
21 | - name: Install packages
22 | working-directory: packages/parrot-devtools
23 | run: npm install
24 | - name: Bundle parrot-devtools
25 | working-directory: packages/parrot-devtools
26 | run: npm run bundle
27 | - name: Deploy extension to WebStore
28 | working-directory: packages/parrot-devtools
29 | env:
30 | CHROME_WEBSTORE_PARROT_DEVTOOLS_APP_ID: ${{ secrets.CHROME_WEBSTORE_PARROT_DEVTOOLS_APP_ID }}
31 | CHROME_WEBSTORE_CLIENT_ID: ${{ secrets.CHROME_WEBSTORE_CLIENT_ID }}
32 | CHROME_WEBSTORE_CLIENT_SECRET: ${{ secrets.CHROME_WEBSTORE_CLIENT_SECRET }}
33 | CHROME_WEBSTORE_REFRESH_TOKEN: ${{ secrets.CHROME_WEBSTORE_REFRESH_TOKEN }}
34 | run: npm run deploy:extension
35 | close_job:
36 | # this job will only run if the PR has been closed without being merged
37 | if: github.event.pull_request.merged == false
38 | runs-on: ubuntu-latest
39 | steps:
40 | - run: |
41 | echo PR #${{ github.event.number }} has been closed without being merged
42 |
--------------------------------------------------------------------------------
/packages/parrot-devtools/__tests__/app/components/Toolbar.spec.jsx:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
13 | * or implied. See the License for the specific language governing
14 | * permissions and limitations under the License.
15 | */
16 |
17 | import React from 'react';
18 | import { shallow } from 'enzyme';
19 |
20 | import Toolbar from '../../../src/app/components/Toolbar';
21 |
22 | describe('Toolbar', () => {
23 | const setFilterValue = jest.fn();
24 | const mockProps = {
25 | filterValue: '',
26 | setFilterValue,
27 | };
28 | it('should render Toolbar component', () => {
29 | const result = shallow();
30 |
31 | expect(result).toMatchSnapshot();
32 | });
33 |
34 | it('invokes setFilterValue', () => {
35 | const result = shallow();
36 |
37 | const event = {
38 | target: { value: 'abc' },
39 | };
40 | result.find('input').simulate('change', event);
41 | expect(setFilterValue).toHaveBeenCalledWith('abc');
42 | });
43 |
44 | it('clear button', () => {
45 | const result = shallow();
46 | result.find('button').simulate('click');
47 | expect(setFilterValue).toHaveBeenCalledWith('');
48 | });
49 | });
50 |
--------------------------------------------------------------------------------
/packages/parrot-devtools/scripts/deploy-to-webstore.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 | // eslint-disable-next-line import/no-extraneous-dependencies
4 | const webstoreClient = require('chrome-webstore-upload');
5 |
6 | const parrotPath = path.join(__dirname, '..', 'parrot-devtools-extension.zip');
7 | const parrotZipFile = fs.createReadStream(parrotPath);
8 | const target = 'default'; // optional. Can also be 'trustedTesters'
9 |
10 | const webStore = webstoreClient({
11 | extensionId: process.env.CHROME_WEBSTORE_PARROT_DEVTOOLS_APP_ID,
12 | clientId: process.env.CHROME_WEBSTORE_CLIENT_ID,
13 | clientSecret: process.env.CHROME_WEBSTORE_CLIENT_SECRET,
14 | refreshToken: process.env.CHROME_WEBSTORE_REFRESH_TOKEN,
15 | });
16 |
17 | webStore
18 | .fetchToken()
19 | .then(token => {
20 | webStore
21 | .uploadExisting(parrotZipFile, token)
22 | .then(uploadResponse => {
23 | // Response is a Resource Representation
24 | // https://developer.chrome.com/webstore/webstore_api/items#resource
25 | console.log(uploadResponse);
26 |
27 | webStore
28 | .publish(target, token)
29 | .then(publishResponse => {
30 | // Response is documented here:
31 | // https://developer.chrome.com/webstore/webstore_api/items/publish
32 | console.log(publishResponse);
33 | })
34 | .catch(publishError =>
35 | console.error(`There was an issue publishing the extension: ${publishError}`)
36 | );
37 | })
38 | .catch(uploadError =>
39 | console.error(`There was an issue uploading the zip file: ${uploadError}`)
40 | );
41 | })
42 | .catch(fetchTokenError =>
43 | console.error(`There was an issue fetching the token: ${fetchTokenError}`)
44 | );
45 |
--------------------------------------------------------------------------------
/packages/parrot-core/src/utils/logger.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
13 | * or implied. See the License for the specific language governing
14 | * permissions and limitations under the License.
15 | */
16 |
17 | /* eslint no-console: 'off' */
18 | import chalk from 'chalk';
19 |
20 | export const loggerColors = {
21 | info: chalk.white,
22 | warn: chalk.yellow,
23 | error: chalk.red,
24 | };
25 |
26 | const infoTemplate = message => loggerColors.info(`Info: ${message}`);
27 | const errorTemplate = message => loggerColors.error(`Error: ${message}`);
28 | const warnTemplate = message => loggerColors.warn(`Warning: ${message}`);
29 |
30 | class Logger {
31 | constructor(output = console.log) {
32 | this.output = output;
33 | this.info = this.baseTemplate(infoTemplate);
34 | this.error = this.baseTemplate(errorTemplate);
35 | this.warn = this.baseTemplate(warnTemplate);
36 | }
37 |
38 | baseTemplate(template) {
39 | return (message, path) =>
40 | this.output(
41 | `[Parrot] ${chalk.underline(path)}` +
42 | ` ${chalk.dim(`(${this.scenario})`)}\n\t${template(message)}`
43 | );
44 | }
45 |
46 | setScenario(scenario) {
47 | this.scenario = scenario;
48 | return this;
49 | }
50 | }
51 |
52 | const logger = new Logger();
53 |
54 | export default logger;
55 |
--------------------------------------------------------------------------------
/packages/parrot-fetch/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | parrot-fetch is an implementation of Parrot that mocks the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API).
6 |
7 | ## Example
8 |
9 | ```js
10 | import parrotFetch from 'parrot-fetch';
11 | import scenarios from './scenarios';
12 |
13 | // fetch will be mocked once the parrotFetch function is called
14 | const parrot = parrotFetch(scenarios);
15 |
16 | // set the scenario to be used
17 | parrot.setActiveScenario('has a ship log');
18 | ```
19 |
20 | ## Mocking a non-global fetch example
21 |
22 | ```js
23 | import parrotFetch from 'parrot-fetch';
24 | import scenarios from './scenarios';
25 |
26 | // include a fetchClient inside of a fetchWrapper object and pass it into the parrotFetch function to mock the fetchClient
27 |
28 | const fetchWrapper = {
29 | fetchClient,
30 | };
31 | const parrot = parrotFetch(scenarios, fetchWrapper);
32 |
33 | // set the scenario to be used
34 | parrot.setActiveScenario('has a ship log');
35 | ```
36 | ### Mocking a non-global fetch - use case example
37 |
38 | An example use case for mocking a non-global fetch could be mocking a fetchClient that is passed in as an extra thunk argument using [redux-thunk withExtraArgument](https://github.com/reduxjs/redux-thunk). Mocking this redux thunk fetchClient would look something like below:
39 |
40 | ```js
41 | import parrotFetch from 'parrot-fetch';
42 | import scenarios from './scenarios';
43 |
44 | const callParrotFetch = () => {
45 | return (dispatch, getState, fetchWrapper) => {
46 | // fetchWrapper = { fetchClient };
47 | return parrotFetch(scenarios, fetchWrapper);
48 | };
49 | };
50 | const parrot = dispatch(callParrotFetch());
51 | parrot.setActiveScenario('has a ship log');
52 | ```
53 |
--------------------------------------------------------------------------------
/packages/parrot-core/src/utils/resolveResponse.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | import getParams from './getParams';
16 |
17 | // eslint-disable-next-line max-params
18 | export default async function resolveResponse(normalizedRequest, platformRequest, mock, resolver) {
19 | if (!mock) {
20 | return resolver();
21 | }
22 |
23 | const { request: { path } = {}, response } = mock;
24 | const { body, status, delay } = await response;
25 | const resolvedResponse = { status };
26 |
27 | const requestWithParams = path
28 | ? {
29 | ...normalizedRequest,
30 | params: getParams(normalizedRequest.path, path),
31 | // headers are lazily computed using accessor in node >15.1.0
32 | headers: normalizedRequest.headers,
33 | }
34 | : normalizedRequest;
35 |
36 | if (typeof body === 'function') {
37 | resolvedResponse.body = await body(requestWithParams, ...[].concat(platformRequest));
38 | } else {
39 | resolvedResponse.body = await body;
40 | }
41 |
42 | if (delay) {
43 | return new Promise(resolve => setTimeout(() => resolve(resolver(resolvedResponse)), delay));
44 | }
45 | return Promise.resolve(resolver(resolvedResponse));
46 | }
47 |
--------------------------------------------------------------------------------
/packages/parrot-server/bin/index.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | /*
4 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
7 | * in compliance with the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software distributed under the License
12 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
13 | * or implied. See the License for the specific language governing permissions and limitations under
14 | * the License.
15 | */
16 |
17 | const yargs = require('yargs');
18 |
19 | const createServer = require('./utils/createServer');
20 |
21 | yargs
22 | .option('port', {
23 | alias: 'p',
24 | describe: 'port to start your parrot server on',
25 | default: 3001,
26 | })
27 | .option('scenarios', {
28 | alias: 's',
29 | describe: 'path to your scenarios file',
30 | demandOption: true,
31 | });
32 |
33 | const startServer = options => {
34 | const { portNumber, pathToScenarios } = options;
35 | const app = createServer(pathToScenarios);
36 |
37 | app.listen(portNumber, error => {
38 | if (error) {
39 | throw new Error(error);
40 | }
41 | console.log(`parrot-server up and listening on port ${portNumber}`); // eslint-disable-line no-console
42 | });
43 | };
44 |
45 | const execute = () => {
46 | const { argv } = yargs;
47 | try {
48 | startServer({ portNumber: argv.port, pathToScenarios: argv.scenarios });
49 | } catch (error) {
50 | console.error('Error starting up parrot-server:', error); // eslint-disable-line no-console
51 | process.exit(1);
52 | }
53 | };
54 |
55 | execute();
56 |
--------------------------------------------------------------------------------
/packages/parrot-devtools/__tests__/app/utils/localStorage.spec.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | import { getLocalStorage, setLocalStorage, PARROT_STATE } from '../../../src/app/utils';
16 |
17 | describe('localStorage', () => {
18 | const { localStorage } = global;
19 |
20 | it('gets serialized value from localStorage', () => {
21 | localStorage.getItem.mockImplementationOnce(() =>
22 | JSON.stringify({
23 | parrot: 'squawk',
24 | })
25 | );
26 | const value = getLocalStorage();
27 | expect(localStorage.getItem).toHaveBeenCalledWith(PARROT_STATE);
28 | expect(value).toMatchObject({ parrot: 'squawk' });
29 | });
30 |
31 | it('gets undefined if key not in localStorage', () => {
32 | localStorage.getItem.mockImplementationOnce(() => null);
33 | const value = getLocalStorage();
34 | expect(localStorage.getItem).toHaveBeenCalledWith(PARROT_STATE);
35 | expect(value).toBeUndefined();
36 | });
37 |
38 | it('sets serialized value in localStorage', () => {
39 | const value = { parrot: 'squawk' };
40 | const serialzed = JSON.stringify(value);
41 | setLocalStorage(value);
42 | expect(localStorage.setItem).toHaveBeenCalledWith(PARROT_STATE, serialzed);
43 | });
44 | });
45 |
--------------------------------------------------------------------------------
/packages/parrot-devtools/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "parrot-devtools",
3 | "version": "5.3.0",
4 | "private": true,
5 | "contributors": [
6 | "Jack Cross ",
7 | "Nathan Force ",
8 | "Jason Schapiro",
9 | "Zach Ripka"
10 | ],
11 | "description": "Devtools for switching Parrot scenarios.",
12 | "scripts": {
13 | "start": "chokidar 'src/**' -c 'npm run build' --initial",
14 | "clean": "rimraf build && rimraf dist",
15 | "prebuild": "npm run clean",
16 | "build": "npm run bundle",
17 | "bundle": "webpack -p && npm run pack:extension && npm run pack:base",
18 | "pack:extension": "node scripts/pack-extension.js",
19 | "pack:base": "node scripts/pack-base.js",
20 | "deploy:extension": "node scripts/deploy-to-webstore.js"
21 | },
22 | "license": "Apache-2.0",
23 | "repository": {
24 | "type": "git",
25 | "url": "https://github.com/americanexpress/parrot.git",
26 | "directory": "packages/parrot-devtools"
27 | },
28 | "dependencies": {
29 | "prop-types": "^15.8.1",
30 | "react": "^16.14.0",
31 | "react-dom": "^16.14.0",
32 | "react-redux": "^7.2.9",
33 | "redux": "^4.2.1",
34 | "styled-components": "^4.4.1",
35 | "whatwg-fetch": "^3.6.17"
36 | },
37 | "devDependencies": {
38 | "@babel/cli": "^7.22.10",
39 | "@babel/core": "^7.22.10",
40 | "@babel/plugin-transform-runtime": "^7.22.10",
41 | "archiver": "^2.1.1",
42 | "babel-loader": "^8.3.0",
43 | "babel-preset-amex": "^3.6.1",
44 | "chokidar": "^3.5.3",
45 | "chokidar-cli": "^2.1.0",
46 | "chrome-webstore-upload": "^0.4.4",
47 | "file-loader": "^5.1.0",
48 | "fs-extra": "^2.1.2",
49 | "import-glob-loader": "^1.1.0",
50 | "rimraf": "^3.0.2",
51 | "url-loader": "^3.0.0",
52 | "webpack": "^4.46.0",
53 | "webpack-cli": "^3.3.12"
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/packages/parrot-devtools/src/app/components/Settings.jsx:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | import React from 'react';
16 | import PropTypes from 'prop-types';
17 | import { connect } from 'react-redux';
18 | import { setUrl as setUrlAction } from '../ducks';
19 | import { Scrollable } from './styled';
20 |
21 | const Settings = ({ url, setUrl }) => (
22 |
23 |
24 |
Settings
25 |
26 |
36 |
37 |
38 |
39 | );
40 |
41 | Settings.propTypes = {
42 | url: PropTypes.string.isRequired,
43 | setUrl: PropTypes.func.isRequired,
44 | };
45 |
46 | export const mapStateToProps = ({ url }) => ({ url });
47 |
48 | export const mapDispatchToProps = dispatch => ({
49 | setUrl: url => dispatch(setUrlAction(url)),
50 | });
51 |
52 | export default connect(mapStateToProps, mapDispatchToProps)(Settings);
53 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## Description
4 |
5 |
6 | ## Motivation and Context
7 |
8 |
9 |
10 | ## How Has This Been Tested?
11 |
12 |
13 |
14 |
15 | ## Types of Changes
16 |
17 | - [ ] Bug fix (non-breaking change which fixes an issue)
18 | - [ ] New feature (non-breaking change which adds functionality)
19 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
20 | - [ ] Documentation (adding or updating documentation)
21 | - [ ] Dependency update
22 |
23 | ## Checklist:
24 |
25 |
26 | - [ ] My change requires a change to the documentation and I have updated the documentation accordingly.
27 | - [ ] My changes are in sync with the code style of this project.
28 | - [ ] There aren't any other open Pull Requests for the same issue/update.
29 | - [ ] These changes should be applied to a maintenance branch.
30 | - [ ] This change requires cross browser checks.
31 | - [ ] This change impacts caching for client browsers.
32 | - [ ] I have added the Apache 2.0 license header to any new files created.
33 |
34 | ## What is the Impact to Developers Using parrot?
35 |
36 |
--------------------------------------------------------------------------------
/examples/pirate-ship-app/client/src/App.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | import React, { Component } from 'react';
16 |
17 | class App extends Component {
18 | state = {
19 | loading: true,
20 | error: false,
21 | };
22 |
23 | async componentDidMount() {
24 | try {
25 | const shipLogResponse = await fetch('/ship_log');
26 | const shipLog = await shipLogResponse.json();
27 | this.setState({ shipLog, loading: false });
28 | } catch (e) {
29 | this.setState({ loading: false, error: true });
30 | }
31 | }
32 |
33 | render() {
34 | const { loading, error, shipLog } = this.state;
35 | if (loading) {
36 | return null;
37 | } else if (error) {
38 | return
79 |
80 | `;
81 |
--------------------------------------------------------------------------------
/packages/parrot-fetch/src/ParrotFetch.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | import Parrot from 'parrot-core';
16 | import parse from 'url-parse';
17 |
18 | class ParrotFetch extends Parrot {
19 | constructor(scenarios, contextFetch) {
20 | super(scenarios);
21 | this.contextFetch = contextFetch;
22 | }
23 |
24 | normalizeRequest = (input, { method = 'GET', ...init } = {}) => {
25 | const { pathname: path, query, ...parsed } = parse(input, true);
26 | const headers = new Headers(init.headers);
27 | let { body } = init;
28 | if (headers.get('Content-Type') === 'application/json') {
29 | body = JSON.parse(body);
30 | }
31 | return {
32 | ...init,
33 | ...parsed,
34 | body,
35 | path,
36 | query: Object.keys(query) && query,
37 | method: method && method.toUpperCase(),
38 | };
39 | };
40 |
41 | resolver = (input, init) => response => {
42 | if (!response) {
43 | return this.contextFetch(input, init);
44 | }
45 | const { body, status } = response;
46 | const responseBlob = new Blob([JSON.stringify(body)], {
47 | type: 'application/json',
48 | });
49 | const responseOptions = {
50 | status,
51 | headers: {
52 | 'Content-Type': 'application/json',
53 | },
54 | };
55 | return Promise.resolve(new Response(responseBlob, responseOptions));
56 | };
57 | }
58 |
59 | export default ParrotFetch;
60 |
--------------------------------------------------------------------------------
/packages/parrot-server/__tests__/__snapshots__/createServer.spec.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`createServer uses parrot middleware 1`] = `
4 | "Array [
5 | Object {
6 | \\"mocks\\": Array [
7 | Object {
8 | \\"request\\": Object {
9 | \\"method\\": \\"GET\\",
10 | \\"path\\": \\"/ship_log\\",
11 | },
12 | \\"response\\": Object {
13 | \\"body\\": Array [
14 | Object {
15 | \\"captain\\": \\"Captain Hook\\",
16 | \\"name\\": \\"The Jolly Roger\\",
17 | },
18 | ],
19 | \\"status\\": 200,
20 | },
21 | },
22 | ],
23 | \\"name\\": \\"has one ship\\",
24 | },
25 | Object {
26 | \\"mocks\\": Array [
27 | Object {
28 | \\"request\\": Object {
29 | \\"method\\": \\"GET\\",
30 | \\"path\\": \\"/ship_log\\",
31 | },
32 | \\"response\\": Object {
33 | \\"body\\": Array [
34 | Object {
35 | \\"captain\\": \\"Captain Hook\\",
36 | \\"name\\": \\"The Jolly Roger\\",
37 | },
38 | Object {
39 | \\"captain\\": \\"Jack Sparrow\\",
40 | \\"name\\": \\"The Black Pearl\\",
41 | },
42 | Object {
43 | \\"captain\\": \\"Davy Jones\\",
44 | \\"name\\": \\"Flying Dutchman\\",
45 | },
46 | Object {
47 | \\"captain\\": \\"Captain Ron\\",
48 | \\"name\\": \\"The Wanderer\\",
49 | },
50 | ],
51 | \\"status\\": 200,
52 | },
53 | },
54 | ],
55 | \\"name\\": \\"has more ships\\",
56 | },
57 | Object {
58 | \\"mocks\\": Array [
59 | Object {
60 | \\"request\\": Object {
61 | \\"method\\": \\"GET\\",
62 | \\"path\\": \\"/ship_log\\",
63 | },
64 | \\"response\\": Object {
65 | \\"status\\": 500,
66 | },
67 | },
68 | ],
69 | \\"name\\": \\"has a server error\\",
70 | },
71 | ]"
72 | `;
73 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "parrot",
3 | "version": "4.0.0",
4 | "license": "Apache-2.0",
5 | "repository": {
6 | "type": "git",
7 | "url": "https://github.com/americanexpress/parrot.git"
8 | },
9 | "contributors": [
10 | "Jack Cross ",
11 | "Nathan Force ",
12 | "Jason Schapiro",
13 | "Jacob Franklin "
14 | ],
15 | "scripts": {
16 | "postinstall": "lerna bootstrap",
17 | "clean": "git clean -d -X",
18 | "test": "jest && npm run test:git-history",
19 | "pretest": "npm run lint",
20 | "lint": "eslint --ext js,jsx,md ./",
21 | "test:git-history": "commitlint --from origin/main --to HEAD",
22 | "test:lockfile": "node lockFileLint.js",
23 | "posttest": "npm run test:lockfile",
24 | "lerna:version": "lerna version",
25 | "lerna:publish": "lerna publish from-package --yes",
26 | "lerna:deploy": "node lernaDeploy.js"
27 | },
28 | "devDependencies": {
29 | "@babel/core": "^7.22.10",
30 | "@babel/polyfill": "^7.12.1",
31 | "@commitlint/cli": "^17.7.1",
32 | "@commitlint/config-conventional": "^8.3.6",
33 | "@testing-library/react-hooks": "^7.0.2",
34 | "@wojtekmaj/enzyme-adapter-react-17": "^0.6.7",
35 | "amex-jest-preset-react": "^8.1.0",
36 | "babel-jest": "^24.9.0",
37 | "babel-preset-amex": "^3.6.1",
38 | "enzyme": "^3.11.0",
39 | "enzyme-to-json": "^3.6.2",
40 | "eslint": "^6.8.0",
41 | "eslint-config-amex": "^11.2.0",
42 | "eslint-config-prettier": "^6.15.0",
43 | "eslint-plugin-prettier": "^3.4.1",
44 | "glob": "^7.2.3",
45 | "husky": "^3.1.0",
46 | "identity-obj-proxy": "^3.0.0",
47 | "isomorphic-fetch": "^2.2.1",
48 | "jest": "^27.5.1",
49 | "lerna": "^3.22.1",
50 | "lockfile-lint": "^4.12.0",
51 | "prettier": "^1.19.1",
52 | "react": "^17.0.0",
53 | "react-dom": "^17.0.2",
54 | "rimraf": "^3.0.2",
55 | "whatwg-fetch": "^3.6.17"
56 | },
57 | "husky": {
58 | "hooks": {
59 | "pre-commit": "npm test",
60 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/packages/parrot-core/__tests__/utils/logger.spec.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | global.console.log = jest.fn();
16 |
17 | const chalk = require('chalk');
18 | const { logger } = require('../../src/utils');
19 | const { loggerColors } = require('../../src/utils/logger');
20 |
21 | describe('logger', () => {
22 | const testLoggerOutput = (logType, logPrefix) => {
23 | const message = `Test ${logPrefix} Message`;
24 | const scenario = 'happyPath';
25 | const path = '/test';
26 | logger.setScenario(scenario);
27 |
28 | logger[logType](message, path);
29 | return (
30 | `[Parrot] ${chalk.underline(path)}` +
31 | ` ${chalk.dim(`(${scenario})`)}\n\t${loggerColors[logType](`${logPrefix}: ${message}`)}`
32 | );
33 | };
34 |
35 | it('can set a scenario', () => {
36 | const scenario = 'bigError';
37 | logger.setScenario(scenario);
38 | expect(logger.scenario).toEqual(scenario);
39 | });
40 |
41 | it('can log an info message', () => {
42 | const expected = testLoggerOutput('info', 'Info');
43 | expect(global.console.log).toHaveBeenCalledWith(expected);
44 | });
45 |
46 | it('can log a warning message', () => {
47 | const expected = testLoggerOutput('warn', 'Warning');
48 | expect(global.console.log).toHaveBeenCalledWith(expected);
49 | });
50 |
51 | it('can log an error message', () => {
52 | const expected = testLoggerOutput('error', 'Error');
53 | expect(global.console.log).toHaveBeenCalledWith(expected);
54 | });
55 | });
56 |
--------------------------------------------------------------------------------
/packages/parrot-friendly/__tests__/Mock.spec.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | import Mock from '../src/Mock';
16 |
17 | describe('Mock', () => {
18 | it('creates initial mock structure', () => {
19 | const mock = new Mock('ahoy');
20 | expect(mock.structure).toEqual('ahoy');
21 | });
22 |
23 | it('adds query', () => {
24 | const mock = new Mock({ request: {} });
25 | mock.query('ahoy');
26 | expect(mock.structure).toEqual({
27 | request: {
28 | query: 'ahoy',
29 | },
30 | });
31 | });
32 |
33 | it('adds headers', () => {
34 | const mock = new Mock({ request: {} });
35 | mock.headers('ahoy');
36 | expect(mock.structure).toEqual({
37 | request: {
38 | headers: 'ahoy',
39 | },
40 | });
41 | });
42 |
43 | it('adds body', () => {
44 | const mock = new Mock({ response: {} });
45 | mock.response('ahoy');
46 | expect(mock.structure).toEqual({
47 | response: {
48 | body: 'ahoy',
49 | },
50 | });
51 | });
52 |
53 | it('adds delay', () => {
54 | const mock = new Mock({ response: {} });
55 | mock.delay('ahoy');
56 | expect(mock.structure).toEqual({
57 | response: {
58 | delay: 'ahoy',
59 | },
60 | });
61 | });
62 |
63 | it('adds status', () => {
64 | const mock = new Mock({ response: {} });
65 | mock.status('ahoy');
66 | expect(mock.structure).toEqual({
67 | response: {
68 | status: 'ahoy',
69 | },
70 | });
71 | });
72 | });
73 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | The following guidelines must be followed by all contributors to this repository. Please review them carefully and do not hesitate to ask for help.
4 |
5 | ### Code of Conduct
6 |
7 | * Review and test your code before submitting a pull request.
8 | * Be kind and professional. Avoid assumptions; oversights happen.
9 | * Be clear and concise when documenting code; focus on value.
10 | * Don't commit commented code to the main repo (stash locally, if needed).
11 |
12 | See [our code of conduct](./CODE_OF_CONDUCT.md) for more details.
13 |
14 | ### Opening the PR
15 |
16 | * [Fork the Parrot repository](https://github.com/americanexpress/parrot/fork), open a PR to `main`, and follow the guidelines outlined in this document.
17 |
18 | ### Pull Request Guidelines
19 |
20 | * Keep PRs small, there should be one change per pull request.
21 |
22 | * All pull requests must have descriptions and a link to corresponding issue(s) if applicable.
23 |
24 | * Keep [commit history clean](https://americanexpress.io/on-the-importance-of-commit-messages/). Follow commit message guidelines (see below) and squash commits as needed to keep a clean history. Remember that your git commit history should tell a story that should be easy to follow for anyone in the future.
25 |
26 | * Before making substantial changes or changes to core functionality and/or architecture [open up an issue](https://github.com/americanexpress/parrot/issues/new) to propose and discuss the changes.
27 |
28 | * Be patient. The review process will be thorough. It must be kept in mind that changes to our repos are platform wide and thus are not taken lightly. Be prepared to defend every single line of code in your pull request. Attempting to rush changes in will not work.
29 |
30 |
31 | ### Git Commit Guidelines
32 |
33 | We follow precise rules for git commit message formatting. These rules make it easier to review commit logs and improve contextual understanding of code changes. This also allows us to auto-generate the CHANGELOG from commit messages and automatically version Parrot during releases.
34 |
35 | For more information on the commit message guidelines we follow see [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/).
36 |
--------------------------------------------------------------------------------
/packages/parrot-server/__tests__/createServer.spec.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | /* eslint-disable global-require */
16 | import request from 'supertest';
17 | import prettyFormat from 'pretty-format';
18 |
19 | describe('createServer', () => {
20 | beforeEach(() => {
21 | jest.resetModules().clearAllMocks();
22 | });
23 |
24 | it('can resolve scenarios file from relative path', () => {
25 | const createServer = require('../bin/utils/createServer');
26 |
27 | expect(() => createServer('./packages/parrot-server/__fixtures__/scenarios.js')).not.toThrow();
28 | });
29 |
30 | it('can resolve scenarios file from absolute path', () => {
31 | const createServer = require('../bin/utils/createServer');
32 |
33 | expect(() => createServer(require.resolve('../__fixtures__/scenarios.js'))).not.toThrow();
34 | });
35 |
36 | it('uses parrot middleware', async () => {
37 | const createServer = require('../bin/utils/createServer');
38 |
39 | const app = createServer(require.resolve('../__fixtures__/scenarios.js'));
40 | const response = await request(app).get('/parrot/scenarios');
41 |
42 | expect(response.status).toBe(200);
43 | expect(prettyFormat(response.body)).toMatchSnapshot();
44 | });
45 |
46 | it('returns an express application', () => {
47 | const mockExpressApp = { use: jest.fn() };
48 | jest.doMock('express', () => () => mockExpressApp);
49 | const createServer = require('../bin/utils/createServer');
50 |
51 | const app = createServer(require.resolve('../__fixtures__/scenarios.js'));
52 | expect(app).toEqual(mockExpressApp);
53 | });
54 | });
55 |
--------------------------------------------------------------------------------
/packages/parrot-core/src/utils/normalizeScenarios.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | function normalizeMock(mock) {
16 | if (typeof mock !== 'function' && (!mock.request || !mock.response)) {
17 | throw new Error('expected mock to be a function');
18 | }
19 |
20 | if (typeof mock === 'function') {
21 | return mock;
22 | }
23 |
24 | const { request, response } = mock;
25 |
26 | const normalizedRequest =
27 | typeof request === 'string' ? { path: request, method: 'GET' } : request;
28 |
29 | let normalizedResponse = response;
30 | if (!response.status && !response.body && !response.delay) {
31 | normalizedResponse = {
32 | status: 200,
33 | body: response,
34 | };
35 | } else if (!response.status) {
36 | normalizedResponse = {
37 | status: 200,
38 | ...response,
39 | };
40 | }
41 |
42 | return { request: normalizedRequest, response: normalizedResponse };
43 | }
44 |
45 | export default function normalizeScenarios(scenarios) {
46 | const normalizedContainer = Array.isArray(scenarios)
47 | ? scenarios.reduce((acc, { name, mocks }) => ({ ...acc, [name]: mocks }), {})
48 | : scenarios;
49 |
50 | return Object.keys(normalizedContainer).reduce(
51 | (acc, name) => ({
52 | ...acc,
53 | [name]: normalizedContainer[name].map((mock, mockIndex) => {
54 | try {
55 | return normalizeMock(mock);
56 | } catch (err) {
57 | throw new Error(
58 | `Mock ${mockIndex} in scenario '${name}' is not an object with keys request and response or a function.`
59 | );
60 | }
61 | }),
62 | }),
63 | {}
64 | );
65 | }
66 |
--------------------------------------------------------------------------------
/packages/parrot-friendly/src/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | import parrotGraphql from 'parrot-graphql';
16 | import Mock from './Mock';
17 |
18 | let scenarios = {};
19 | let scenario;
20 |
21 | function createMethod(method) {
22 | return path => {
23 | const initialMock = new Mock({
24 | request: {
25 | method,
26 | path,
27 | },
28 | response: {},
29 | });
30 | scenario.push(initialMock.structure);
31 | return initialMock;
32 | };
33 | }
34 |
35 | export const get = createMethod('GET');
36 | export const head = createMethod('HEAD');
37 | export const post = createMethod('POST');
38 | export const put = createMethod('PUT');
39 | export const del = createMethod('DELETE');
40 | export const connect = createMethod('CONNECT');
41 | export const options = createMethod('OPTIONS');
42 | export const patch = createMethod('PATCH');
43 |
44 | export function mock(structure) {
45 | const initialMock = new Mock(structure);
46 | scenario.push(initialMock.structure);
47 | return initialMock;
48 | }
49 |
50 | export function request(structure) {
51 | const initialMock = new Mock({
52 | request: structure,
53 | response: {},
54 | });
55 | scenario.push(initialMock.structure);
56 | return initialMock;
57 | }
58 |
59 | export function graphql(path, schema, mocks) {
60 | const initialMock = new Mock(parrotGraphql(path, schema, mocks));
61 | scenario.push(initialMock.structure);
62 | return initialMock;
63 | }
64 |
65 | export function describe(name, block) {
66 | scenarios = {};
67 | block();
68 | return scenarios;
69 | }
70 |
71 | export function it(name, block) {
72 | scenarios[name] = [];
73 | scenario = scenarios[name];
74 | block();
75 | }
76 |
--------------------------------------------------------------------------------
/packages/parrot-middleware/__tests__/index.spec.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | import { Router } from 'express';
16 | import bodyParser from 'body-parser';
17 | import ParrotMiddleware from '../src/ParrotMiddleware';
18 | import parrotMiddleware from '../src';
19 |
20 | jest.mock('express', () => {
21 | const req = { body: { scenario: 'squawk' } };
22 | const res = {
23 | sendStatus: jest.fn(),
24 | json: jest.fn(),
25 | };
26 | const post = jest.fn((path, cb) => cb(req, res));
27 | const get = jest.fn((path, cb) => cb(req, res));
28 | return {
29 | Router: () => ({ post, get, res }),
30 | };
31 | });
32 | jest.mock('body-parser', () => ({ json: jest.fn() }));
33 | jest.mock(
34 | '../src/ParrotMiddleware',
35 | () =>
36 | class Mock {
37 | static getActiveScenario = jest.fn();
38 |
39 | static setActiveScenario = jest.fn();
40 |
41 | static getScenarios = jest.fn();
42 |
43 | getActiveScenario = Mock.getActiveScenario;
44 |
45 | setActiveScenario = Mock.setActiveScenario;
46 |
47 | getScenarios = Mock.getScenarios;
48 | }
49 | );
50 |
51 | describe('parrot-middleware', () => {
52 | it('should set up the middleware', () => {
53 | const middleware = parrotMiddleware();
54 | const router = Router();
55 |
56 | expect(bodyParser.json).toHaveBeenCalled();
57 | expect(router.post).toHaveBeenCalled();
58 | expect(router.get).toHaveBeenCalled();
59 | expect(router.res.sendStatus).toHaveBeenCalled();
60 | expect(router.res.json).toHaveBeenCalled();
61 | expect(ParrotMiddleware.getActiveScenario).toHaveBeenCalled();
62 | expect(ParrotMiddleware.setActiveScenario).toHaveBeenCalled();
63 | expect(ParrotMiddleware.getScenarios).toHaveBeenCalled();
64 | expect(middleware).toEqual(expect.any(Array));
65 | });
66 | });
67 |
--------------------------------------------------------------------------------
/packages/parrot-core/src/Parrot.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | import { normalizeScenarios, matchMock, resolveResponse, logger } from './utils';
16 |
17 | class Parrot {
18 | constructor(scenarios = {}) {
19 | this.scenarios = normalizeScenarios(scenarios);
20 | [this.activeScenario] = Object.keys(scenarios);
21 | logger.setScenario(this.activeScenario);
22 | this.logger = logger;
23 | }
24 |
25 | getActiveScenario = () => this.activeScenario;
26 |
27 | setActiveScenario = name => {
28 | this.activeScenario = name;
29 | logger.setScenario(name);
30 | };
31 |
32 | getScenarios = () =>
33 | Object.keys(this.scenarios).map(name => ({ name, mocks: this.scenarios[name] }));
34 |
35 | setScenarios = scenarios => {
36 | this.scenarios = normalizeScenarios(scenarios);
37 | };
38 |
39 | getScenario = name => this.scenarios[name];
40 |
41 | setScenario = (name, mocks) => {
42 | const scenarios = { [name]: mocks };
43 | this.scenarios = {
44 | ...this.scenarios,
45 | ...normalizeScenarios(scenarios),
46 | };
47 | };
48 |
49 | getMock = (name, index) => this.scenarios[name][index];
50 |
51 | setMock = (name, index, mock) => {
52 | this.scenarios[name][index] = mock;
53 | };
54 |
55 | resolve = async (...platformRequest) => {
56 | const normalizedRequest = this.normalizeRequest(...platformRequest);
57 | const resolver = this.resolver(...platformRequest);
58 | const activeScenarioOverride =
59 | this.getActiveScenarioOverride && this.getActiveScenarioOverride(...platformRequest);
60 | const mocks = this.scenarios[activeScenarioOverride || this.activeScenario];
61 | const mock = await matchMock(normalizedRequest, platformRequest, mocks);
62 | return resolveResponse(normalizedRequest, platformRequest, mock, resolver);
63 | };
64 | }
65 |
66 | export default Parrot;
67 |
--------------------------------------------------------------------------------
/packages/parrot-devtools/__tests__/app/components/Scenarios.spec.jsx:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
13 | * or implied. See the License for the specific language governing
14 | * permissions and limitations under the License.
15 | */
16 |
17 | import React from 'react';
18 | import { shallow } from 'enzyme';
19 |
20 | import ScenariosMiddleware from '../../../src/app/components/Scenarios';
21 |
22 | const scenario = 'api-fetch';
23 | const setScenario = jest.fn(() => Promise.resolve());
24 | const scenariosData = {
25 | scenario,
26 | setScenario,
27 | loading: false,
28 | filteredScenarios: [{ name: scenario }, { name: 'scenario-2' }],
29 | loadScenarios: jest.fn(() => Promise.resolve()),
30 | };
31 | const mockProps = { scenariosData };
32 |
33 | describe('Scenarios', () => {
34 | beforeEach(() => {
35 | jest.clearAllMocks();
36 | });
37 |
38 | describe('ScenariosMiddleware', () => {
39 | it('should render scenarios middleware component', () => {
40 | const result = shallow();
41 |
42 | expect(result).toMatchSnapshot();
43 |
44 | result
45 | .find('button')
46 | .first()
47 | .simulate('click');
48 |
49 | expect(setScenario).toHaveBeenCalledWith(scenario);
50 | });
51 |
52 | it('should loading state', () => {
53 | const props = {
54 | scenariosData: {
55 | ...scenariosData,
56 | loading: true,
57 | },
58 | };
59 | const result = shallow();
60 |
61 | expect(result).toMatchSnapshot();
62 | });
63 |
64 | it('should inform the user of failure', () => {
65 | const props = {
66 | scenariosData: {
67 | ...scenariosData,
68 | loading: false,
69 | scenario: '',
70 | scenarios: [],
71 | },
72 | };
73 | const result = shallow();
74 |
75 | expect(result).toMatchSnapshot();
76 | });
77 | });
78 | });
79 |
--------------------------------------------------------------------------------
/packages/parrot-fetch/__tests__/ParrotFetch.spec.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | import ParrotFetch from '../src/ParrotFetch';
16 |
17 | jest.mock('parrot-core', () => class {});
18 |
19 | describe('ParrotFetch', () => {
20 | it('should normalize', () => {
21 | const parrotFetch = new ParrotFetch();
22 | const normalized = parrotFetch.normalizeRequest('http://www.parrot.com/squawk?ahoy=matey');
23 | expect(normalized).toMatchObject({
24 | path: '/squawk',
25 | query: {
26 | ahoy: 'matey',
27 | },
28 | protocol: 'http:',
29 | host: 'www.parrot.com',
30 | });
31 | });
32 |
33 | it('should parse the body is content-type is json', () => {
34 | const parrotFetch = new ParrotFetch();
35 | const normalized = parrotFetch.normalizeRequest('http://www.parrot.com/squawk', {
36 | headers: {
37 | 'content-type': 'application/json',
38 | },
39 | body: JSON.stringify({ ahoy: 'matey' }),
40 | });
41 | expect(normalized).toMatchObject({
42 | path: '/squawk',
43 | body: {
44 | ahoy: 'matey',
45 | },
46 | protocol: 'http:',
47 | host: 'www.parrot.com',
48 | });
49 | });
50 |
51 | it('should resolve to context fetch', () => {
52 | const input = 'http://www.parrot.com';
53 | const contextFetch = jest.fn(() => 'ahoy');
54 | const parrotFetch = new ParrotFetch({}, contextFetch);
55 | const resolved = parrotFetch.resolver(input)();
56 | expect(contextFetch).toHaveBeenCalledWith(input, undefined);
57 | expect(resolved).toBe('ahoy');
58 | });
59 |
60 | it('should resolve response', async () => {
61 | const input = 'http://www.parrot.com';
62 | const contextFetch = jest.fn();
63 | const parrotFetch = new ParrotFetch({}, contextFetch);
64 | const resolved = parrotFetch.resolver(input)({ body: 'ahoy', status: 204 });
65 | expect(contextFetch).not.toHaveBeenCalled();
66 | const data = await resolved;
67 | await expect(data.text()).resolves.toBe('"ahoy"');
68 | expect(data.status).toBe(204);
69 | expect(Object.fromEntries([...data.headers])).toEqual({
70 | 'content-type': 'application/json',
71 | });
72 | });
73 | });
74 |
--------------------------------------------------------------------------------
/packages/parrot-devtools/src/app/components/Scenarios.jsx:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
13 | * or implied. See the License for the specific language governing
14 | * permissions and limitations under the License.
15 | */
16 |
17 | import React from 'react';
18 | import PropTypes from 'prop-types';
19 |
20 | import { Grid, Scrollable } from './styled';
21 |
22 | function ScenariosMiddleware({ scenariosData }) {
23 | const { loading, filteredScenarios, scenario, setScenario, loadScenarios } = scenariosData;
24 |
25 | if (loading) {
26 | return (
27 |
28 |
29 |
30 | );
31 | }
32 |
33 | if (!filteredScenarios || !scenario) {
34 | return (
35 |
36 |
37 |
38 |
Failed to Load Scenarios
39 |
40 | Try
41 |
44 | or updating the middleware URL in settings.
45 |
46 |
47 |
48 | );
49 | }
50 |
51 | return (
52 |
53 |
54 | {filteredScenarios.map(({ name }) => (
55 |
56 |
66 |
67 | ))}
68 |
69 |
70 | );
71 | }
72 |
73 | ScenariosMiddleware.propTypes = {
74 | scenariosData: PropTypes.shape({
75 | loading: PropTypes.bool,
76 | filteredScenarios: PropTypes.arrayOf(
77 | PropTypes.shape({
78 | name: PropTypes.string.isRequired,
79 | })
80 | ),
81 | scenario: PropTypes.string,
82 | setScenario: PropTypes.func,
83 | loadScenarios: PropTypes.func,
84 | }).isRequired,
85 | };
86 |
87 | export default ScenariosMiddleware;
88 |
--------------------------------------------------------------------------------
/packages/parrot-friendly/__tests__/index.spec.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | import { describe as parrotDescribe, it as parrotIt, get, mock, request, graphql } from '../src';
16 |
17 | jest.mock('parrot-graphql', () => () => 'squawk');
18 |
19 | describe('Friendly methods', () => {
20 | it('describe calls function passed and returns scenarios', () => {
21 | const block = jest.fn();
22 | const scenarios = parrotDescribe('squawk', block);
23 | expect(scenarios).toEqual({});
24 | expect(block).toHaveBeenCalled();
25 | });
26 |
27 | it('it calls function passed', () => {
28 | const block = jest.fn();
29 | parrotIt('squawk', block);
30 | expect(block).toHaveBeenCalled();
31 | });
32 |
33 | it('HTTP methods return mock object', () => {
34 | const createdMock = get('/polly/wanna/cracker');
35 | expect(createdMock).toMatchObject({
36 | structure: expect.any(Object),
37 | query: expect.any(Function),
38 | headers: expect.any(Function),
39 | response: expect.any(Function),
40 | delay: expect.any(Function),
41 | status: expect.any(Function),
42 | });
43 | });
44 |
45 | it('mock returns mock object', () => {
46 | const createdMock = mock('squawk');
47 | expect(createdMock).toMatchObject({
48 | structure: 'squawk',
49 | query: expect.any(Function),
50 | headers: expect.any(Function),
51 | response: expect.any(Function),
52 | delay: expect.any(Function),
53 | status: expect.any(Function),
54 | });
55 | });
56 |
57 | it('request returns mock object', () => {
58 | const createdMock = request('squawk');
59 | expect(createdMock).toMatchObject({
60 | structure: {
61 | request: 'squawk',
62 | response: {},
63 | },
64 | query: expect.any(Function),
65 | headers: expect.any(Function),
66 | response: expect.any(Function),
67 | delay: expect.any(Function),
68 | status: expect.any(Function),
69 | });
70 | });
71 |
72 | it('graphql returns parrotGraphql function', () => {
73 | const createdMock = graphql();
74 | expect(createdMock).toMatchObject({
75 | structure: 'squawk',
76 | query: expect.any(Function),
77 | headers: expect.any(Function),
78 | response: expect.any(Function),
79 | delay: expect.any(Function),
80 | status: expect.any(Function),
81 | });
82 | });
83 | });
84 |
--------------------------------------------------------------------------------
/packages/parrot-devtools/src/app/hooks/useScenarios.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2019 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
13 | * or implied. See the License for the specific language governing
14 | * permissions and limitations under the License.
15 | */
16 |
17 | import React from 'react';
18 |
19 | export default function useScenarios(url, initialScenario) {
20 | const [loading, setLoading] = React.useState(false);
21 | const [scenario, setScenario] = React.useState('');
22 | const [scenarios, setScenarios] = React.useState([]);
23 |
24 | const methods = React.useMemo(
25 | () => ({
26 | loadScenarios: async () => {
27 | setLoading(true);
28 | try {
29 | const scenariosResponse = await fetch(`${url}/parrot/scenarios`);
30 | const scenarioResponse = await fetch(`${url}/parrot/scenario`);
31 | // eslint-disable-next-line prettier/prettier
32 | setScenario( await scenarioResponse.json() );
33 | // eslint-disable-next-line prettier/prettier
34 | setScenarios( await scenariosResponse.json() );
35 | } catch (e) {
36 | // do nothing
37 | }
38 | setLoading(false);
39 | },
40 | setScenario: async selectedScenario => {
41 | try {
42 | await fetch(`${url}/parrot/scenario`, {
43 | method: 'POST',
44 | headers: {
45 | 'Content-Type': 'application/json',
46 | },
47 | body: JSON.stringify({
48 | scenario: selectedScenario,
49 | }),
50 | });
51 |
52 | setScenario(selectedScenario);
53 |
54 | if ('chrome' in window) {
55 | chrome.devtools.inspectedWindow.reload(null);
56 | }
57 | } catch (e) {
58 | // do nothing
59 | }
60 | },
61 | }),
62 | [url]
63 | );
64 |
65 | React.useLayoutEffect(() => {
66 | if (initialScenario) methods.setScenario(initialScenario);
67 | methods.loadScenarios();
68 | }, [url, initialScenario]);
69 |
70 | const [filteredScenarios, setFilteredScenarios] = React.useState(scenarios);
71 | const [filterValue, setFilterValue] = React.useState('');
72 |
73 | React.useEffect(() => {
74 | if (filterValue === '') {
75 | setFilteredScenarios(scenarios);
76 | } else {
77 | setFilteredScenarios(
78 | scenarios.filter(({ name }) => name.toLowerCase().includes(filterValue))
79 | );
80 | }
81 | }, [filterValue, scenarios]);
82 |
83 | return {
84 | loading,
85 | scenario,
86 | filteredScenarios,
87 | filterValue,
88 | setFilterValue,
89 | ...methods,
90 | };
91 | }
92 |
--------------------------------------------------------------------------------
/packages/parrot-core/src/utils/matchMock.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | import util from 'util';
16 | import isEqual from 'lodash/isEqual';
17 | import { match } from 'path-to-regexp';
18 | import logger from './logger';
19 |
20 | function matchRequest(normalizedRequest) {
21 | return request =>
22 | Object.keys(request).every(property => {
23 | if (property === 'path') {
24 | if (request.path instanceof RegExp) {
25 | return request.path.test(normalizedRequest.path);
26 | }
27 | const matchRoute = match(request.path);
28 | const result = matchRoute(normalizedRequest.path);
29 | return result !== false;
30 | }
31 | if (property === 'headers') {
32 | return Object.keys(request.headers).every(
33 | key => request.headers[key] === normalizedRequest.headers[key]
34 | );
35 | }
36 |
37 | return isEqual(normalizedRequest[property], request[property]);
38 | });
39 | }
40 |
41 | export default function matchMock(normalizedRequest, platformRequest, mocks) {
42 | let matchedMock;
43 |
44 | if (!Array.isArray(mocks)) {
45 | throw new TypeError(`mocks is not an array as expected. What was passed: ${mocks}`);
46 | }
47 |
48 | if (mocks.length === 0) {
49 | throw new TypeError('mocks is empty, and likely none are defined for the current scenario.');
50 | }
51 |
52 | for (let index = 0; index < mocks.length; index += 1) {
53 | const mock = mocks[index];
54 | if (typeof mock === 'function') {
55 | const response = mock(
56 | normalizedRequest,
57 | matchRequest(normalizedRequest),
58 | ...[].concat(platformRequest)
59 | );
60 | if (response) {
61 | matchedMock = { response };
62 | logger.info('Matched mock function.', normalizedRequest.path);
63 | break;
64 | }
65 | } else if (
66 | typeof mock.request === 'function' &&
67 | mock.request(normalizedRequest, matchRequest(normalizedRequest))
68 | ) {
69 | logger.info('Matched request function.', normalizedRequest.path);
70 | matchedMock = mock;
71 | break;
72 | } else if (typeof mock.request === 'object' && matchRequest(normalizedRequest)(mock.request)) {
73 | logger.info(
74 | `Matched request object: ${util.inspect(mock.request, {
75 | colors: true,
76 | breakLength: Infinity,
77 | })}`,
78 | normalizedRequest.path
79 | );
80 | matchedMock = mock;
81 | break;
82 | }
83 | }
84 |
85 | return matchedMock;
86 | }
87 |
--------------------------------------------------------------------------------
/packages/parrot-core/__tests__/utils/normalizeScenarios.spec.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | import { normalizeScenarios } from '../../src/utils';
16 |
17 | describe('normalizeScenarios', () => {
18 | it('normalizes scenarios array', () => {
19 | const scenarios = [
20 | {
21 | name: 'default',
22 | mocks: [
23 | {
24 | request: 'squawk',
25 | response: 'squawk',
26 | },
27 | ],
28 | },
29 | ];
30 | const {
31 | default: [
32 | {
33 | request: { path },
34 | response: { body },
35 | },
36 | ],
37 | } = normalizeScenarios(scenarios);
38 | expect(path).toEqual(scenarios[0].mocks[0].request);
39 | expect(body).toEqual(scenarios[0].mocks[0].response);
40 | });
41 |
42 | it('normalizes scenarios object', () => {
43 | const scenarios = {
44 | default: [
45 | {
46 | request: 'squawk',
47 | response: {
48 | body: 'squawk',
49 | delay: 123,
50 | },
51 | },
52 | ],
53 | };
54 | const {
55 | default: [
56 | {
57 | request: { path },
58 | response: { body },
59 | },
60 | ],
61 | } = normalizeScenarios(scenarios);
62 | expect(path).toEqual(scenarios.default[0].request);
63 | expect(body).toEqual(scenarios.default[0].response.body);
64 | });
65 |
66 | it('does not throw an error for correct mock object', () => {
67 | const scenarios = {
68 | default: [
69 | {
70 | request: {
71 | path: 'squawk',
72 | method: 'GET',
73 | },
74 | response: {
75 | body: 'squawk',
76 | status: 200,
77 | },
78 | },
79 | ],
80 | };
81 | const {
82 | default: [{ request, response }],
83 | } = normalizeScenarios(scenarios);
84 | expect(request).toEqual(scenarios.default[0].request);
85 | expect(response).toEqual({ ...scenarios.default[0].response, status: 200 });
86 | });
87 |
88 | it('throws an error for incorrect mock object', () => {
89 | const scenarios = {
90 | default: [{}],
91 | };
92 | expect(() => normalizeScenarios(scenarios)).toThrow();
93 | });
94 |
95 | it('does not throw an error for mock function', () => {
96 | const scenarios = { default: [() => null] };
97 | const { default: defaultScenario } = normalizeScenarios(scenarios);
98 | expect(defaultScenario).toEqual(scenarios.default);
99 | });
100 | });
101 |
--------------------------------------------------------------------------------
/packages/parrot-core/__tests__/utils/matchMock.spec.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | import { matchMock, logger } from '../../src/utils';
16 |
17 | jest.mock('../../src/utils/logger', () => ({
18 | info: jest.fn(),
19 | }));
20 |
21 | describe('matchMock', () => {
22 | it('calls mock function that does not match', () => {
23 | const mocks = [jest.fn(() => false)];
24 | matchMock({}, {}, mocks);
25 | expect(mocks[0]).toHaveBeenCalled();
26 | expect(logger.info).not.toHaveBeenCalled();
27 | });
28 |
29 | it('calls mock function that matches', () => {
30 | const mocks = [jest.fn(() => true)];
31 | matchMock({}, {}, mocks);
32 | expect(logger.info).toHaveBeenCalled();
33 | });
34 |
35 | it('calls mock request function that matches', () => {
36 | const mocks = [{ request: jest.fn(() => true) }];
37 | expect(matchMock({}, {}, mocks)).toEqual(mocks[0]);
38 | });
39 |
40 | it('does not match mock object', () => {
41 | const mocks = [{ request: { path: '/squawk', headers: 'ahoy', 'Keep-Alive': 'timeout=5' } }];
42 | const req = { path: '/squawk', headers: 'matey', 'Keep-Alive': 'timeout=5' };
43 | expect(matchMock(req, {}, mocks)).toBe(undefined);
44 | });
45 |
46 | it('matches mock object', () => {
47 | const mocks = [{ request: { path: '/squawk', headers: 'ahoy', 'Keep-Alive': 'timeout=5' } }];
48 | const req = { path: '/squawk', headers: 'ahoy', 'Keep-Alive': 'timeout=5' };
49 | expect(matchMock(req, {}, mocks)).toEqual(mocks[0]);
50 | });
51 |
52 | it('matches mock object when path variable', () => {
53 | const mocks = [
54 | { request: { path: '/squawk/:id', headers: 'ahoy', 'Keep-Alive': 'timeout=5' } },
55 | ];
56 | const req = { path: '/squawk/123', headers: 'ahoy', 'Keep-Alive': 'timeout=5' };
57 | expect(matchMock(req, {}, mocks)).toEqual(mocks[0]);
58 | });
59 |
60 | it('matches mock object when regex path', () => {
61 | const mocks = [
62 | { request: { path: /^\/squawk\/\d?/, headers: 'ahoy', 'Keep-Alive': 'timeout=5' } },
63 | ];
64 | const req = { path: '/squawk/123', headers: 'ahoy', 'Keep-Alive': 'timeout=5' };
65 | expect(matchMock(req, {}, mocks)).toEqual(mocks[0]);
66 | });
67 |
68 | it('throws when mocks is not an array', () => {
69 | const mocks = {};
70 | expect(() => matchMock({}, {}, mocks)).toThrow(
71 | 'mocks is not an array as expected. What was passed: [object Object]'
72 | );
73 | });
74 |
75 | it('throws when mocks is an empty array', () => {
76 | const mocks = [];
77 | expect(() => matchMock({}, {}, mocks)).toThrow(
78 | 'mocks is empty, and likely none are defined for the current scenario.'
79 | );
80 | });
81 | });
82 |
--------------------------------------------------------------------------------
/packages/parrot-server/__tests__/index.spec.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | import { spawn, spawnSync } from 'child_process';
16 | import fetch from 'node-fetch';
17 | import waitPort from 'wait-port';
18 |
19 | const runningParrotServerProcesses = [];
20 |
21 | jest.setTimeout(10000);
22 |
23 | const executeParrotServerCommand = async parrotServerArgs => {
24 | const { portNumber, pathToScenarios } = parrotServerArgs;
25 |
26 | const args = [require.resolve('../bin/index.js'), '--scenarios', pathToScenarios];
27 | if (portNumber) {
28 | args.push('--port', portNumber);
29 | }
30 | const parrotServerProcess = spawn('node', args);
31 |
32 | /* eslint-disable no-console */
33 | parrotServerProcess.stdout.on('data', data => console.log(data.toString()));
34 | parrotServerProcess.stderr.on('data', data => console.error(data.toString()));
35 | /* eslint-enable */
36 | let isParrotUp;
37 | try {
38 | isParrotUp = await waitPort({ port: portNumber || 3001, timeout: 5000, output: 'silent' });
39 | } catch (error) {
40 | throw new Error(error);
41 | }
42 | if (isParrotUp) {
43 | runningParrotServerProcesses.push(parrotServerProcess);
44 | return parrotServerProcess;
45 | }
46 |
47 | throw new Error('parrot-server did not start up within 5 seconds');
48 | };
49 |
50 | it('defaults to port 3001 if --port is not given', async () => {
51 | const args = { pathToScenarios: require.resolve('../__fixtures__/scenarios.js') };
52 | await executeParrotServerCommand(args);
53 |
54 | const response = await fetch('http://localhost:3001/parrot/scenarios');
55 | expect(response.status).toBe(200);
56 | });
57 |
58 | it('starts server on given port', async () => {
59 | const portNumber = 3005;
60 | const args = {
61 | pathToScenarios: require.resolve('../__fixtures__/scenarios.js'),
62 | portNumber,
63 | };
64 |
65 | await executeParrotServerCommand(args);
66 |
67 | const response = await fetch(`http://localhost:${portNumber}/parrot/scenarios`);
68 | expect(response.status).toBe(200);
69 | });
70 |
71 | it('exits with status 1 if something goes wrong while starting the server', () => {
72 | expect.assertions(1);
73 |
74 | const args = [
75 | require.resolve('../bin/index.js'),
76 | '--scenarios',
77 | '../__fixtures__/not-a-valid-scenarios-file.js',
78 | ];
79 |
80 | const parrotServerProcess = spawnSync('node', args, { timeout: 5000 });
81 |
82 | /* eslint-disable no-console */
83 | console.log(parrotServerProcess.stderr.toString());
84 | console.log(parrotServerProcess.stdout.toString());
85 | /* eslint-enable */
86 |
87 | expect(parrotServerProcess.status).toBe(1);
88 | });
89 |
90 | afterAll(() => {
91 | runningParrotServerProcesses.forEach(process => process.kill());
92 | });
93 |
--------------------------------------------------------------------------------
/packages/parrot-core/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | parrot-core abstracts the matching, logging, and resolving functionality of Parrot away from each implementation. [parrot-middleware](https://github.com/americanexpress/parrot/blob/main/packages/parrot-middleware) and [parrot-fetch](https://github.com/americanexpress/parrot/blob/main/packages/parrot-fetch) use parrot-core and any new implementations could extend parrot-core in a similar way.
6 |
7 | ## Example Implementation
8 |
9 | ```js
10 | import Parrot from 'parrot-core';
11 |
12 | class ParrotNew extends Parrot {
13 | constructor(scenarios) {
14 | super(scenarios);
15 | // any constructor logic that is needed
16 | }
17 |
18 | normalizeRequest = request => {
19 | // conform incoming requests to match the scenarios structure
20 | };
21 |
22 | resolver = request => response => {
23 | // resolve the matched response to the implementation platform
24 | };
25 | }
26 |
27 | export default ParrotNew;
28 | ```
29 |
30 | ## Access Methods
31 |
32 | parrot-core also defines several methods that can be used to interact with the scenarios that are passed in.
33 |
34 | ### `getActiveScenario()`
35 |
36 | Returns the name of the currently active scenario.
37 |
38 | ### `setActiveScenario(name)`
39 |
40 | Sets the currently active scenario.
41 |
42 | #### Arguments
43 |
44 | - `name` (_String_): Scenario name.
45 |
46 | ### `getScenarios()`
47 |
48 | Returns an array of scenario objects.
49 |
50 | ### `setScenarios(scenarios)`
51 |
52 | Sets `scenarios` as the array of available scenarios.
53 |
54 | #### Arguments
55 |
56 | - `scenarios` (_Array_ or _Object_): Scenarios descriptor.
57 |
58 | ### `getScenario(name)`
59 |
60 | Returns the scenario object with matching `name`.
61 |
62 | #### Arguments
63 |
64 | - `name` (_String_): Scenario name.
65 |
66 | ### `setScenario(name, mocks)`
67 |
68 | Sets the mocks for scenario with matching `name`.
69 |
70 | #### Arguments
71 |
72 | - `name` (_String_): Scenario name.
73 | - `mocks` (_Array_): Array of mock objects.
74 |
75 | ### `getMock(name, index)`
76 |
77 | Returns the mock at `index` for scenario with matching `name`.
78 |
79 | #### Arguments
80 |
81 | - `name` (_String_): Scenario name.
82 | - `index` (_Number_): Mock index.
83 |
84 | ### `setMock(name, index, mock)`
85 |
86 | Sets the mock at `index` for scenario with matching `name`.
87 |
88 | #### Arguments
89 |
90 | - `name` (_String_): Scenario name.
91 | - `index` (_Number_): Mock index.
92 | - `mock` (_Object_): Mock object.
93 |
94 | ## Utility Methods
95 |
96 | ### `getParams(path, route)`
97 |
98 | Extracts the route parameters from a given `path`, using the specified `route`.
99 |
100 | This is useful when using the manual `match` function.
101 |
102 | #### Arguments
103 |
104 | - `path` (_String_): Requested URL path.
105 | - `route` (_String_): [Express-style](https://expressjs.com/en/guide/routing.html) route with route parameters.
106 |
107 | #### Example Usage
108 |
109 | ```javascript
110 | const { getParams } = require('parrot-core');
111 |
112 | const getBook = ({ url }, match) => {
113 | const path = '/books/:bookId';
114 | if (match({ path, method: 'GET' })) {
115 | const { bookId } = getParams(url, path);
116 | const requestedBook = books.find(book => book.bookId === bookId);
117 | if (!requestedBook) {
118 | return { status: 404 };
119 | }
120 | return { status: 200, body: requestedBook };
121 | }
122 | return null;
123 | };
124 | ```
125 |
--------------------------------------------------------------------------------
/packages/parrot-core/__tests__/utils/resolveResponse.spec.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | import { resolveResponse } from '../../src/utils';
16 |
17 | describe('resolveResponse', () => {
18 | expect.assertions(1);
19 |
20 | let resolver;
21 | beforeEach(() => {
22 | resolver = jest.fn();
23 | });
24 |
25 | it('handles undefined mock', async () => {
26 | resolveResponse({}, {}, undefined, resolver);
27 |
28 | expect(resolver).toHaveBeenCalled();
29 | });
30 |
31 | it('handles mock without request', async () => {
32 | expect.assertions(1);
33 |
34 | const mock = {
35 | response: {
36 | body: 'squawk',
37 | },
38 | };
39 | await resolveResponse({}, {}, mock, resolver);
40 |
41 | expect(resolver).toHaveBeenCalledWith({ body: 'squawk' });
42 | });
43 |
44 | it('sets params', async () => {
45 | expect.assertions(1);
46 |
47 | const mock = {
48 | request: {
49 | path: '/:ahoy',
50 | },
51 | response: {
52 | body: jest.fn(req => req),
53 | },
54 | };
55 | await resolveResponse({ path: '/squawk' }, {}, mock, resolver);
56 |
57 | expect(mock.response.body).toHaveBeenCalledWith(
58 | {
59 | path: '/squawk',
60 | params: {
61 | ahoy: 'squawk',
62 | },
63 | },
64 | {}
65 | );
66 | });
67 |
68 | it('does not set params when no path params', async () => {
69 | expect.assertions(1);
70 |
71 | const mock = {
72 | request: {
73 | path: '/squawk',
74 | },
75 | response: {
76 | body: jest.fn(req => req),
77 | },
78 | };
79 | await resolveResponse({ path: '/squawk' }, {}, mock, resolver);
80 |
81 | expect(mock.response.body).toHaveBeenCalledWith(
82 | {
83 | path: '/squawk',
84 | params: {},
85 | },
86 | {}
87 | );
88 | });
89 |
90 | it('does not set params when no match', async () => {
91 | expect.assertions(1);
92 |
93 | const mock = {
94 | request: {
95 | path: '/:ahoy',
96 | },
97 | response: {
98 | body: jest.fn(req => req),
99 | },
100 | };
101 | await resolveResponse({ path: '/' }, {}, mock, resolver);
102 |
103 | expect(mock.response.body).toHaveBeenCalledWith(
104 | {
105 | path: '/',
106 | params: {},
107 | },
108 | {}
109 | );
110 | });
111 |
112 | it('does not set params when no path', async () => {
113 | expect.assertions(1);
114 |
115 | const mock = {
116 | request: {},
117 | response: {
118 | body: jest.fn(req => req),
119 | },
120 | };
121 | await resolveResponse({}, {}, mock, resolver);
122 |
123 | expect(mock.response.body).toHaveBeenCalledWith({}, {});
124 | });
125 |
126 | it('delays resolving response', async () => {
127 | expect.assertions(1);
128 |
129 | global.setTimeout = jest.fn(fn => fn());
130 | const mock = {
131 | request: {},
132 | response: {
133 | body: 'squawk',
134 | delay: 1234,
135 | },
136 | };
137 | await resolveResponse({}, {}, mock, resolver);
138 |
139 | expect(global.setTimeout).toHaveBeenCalledWith(expect.any(Function), 1234);
140 | });
141 | });
142 |
--------------------------------------------------------------------------------
/packages/parrot-friendly/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | parrot-friendly is a helper library that allows you to write [scenarios](https://github.com/americanexpress/parrot/blob/main/SCENARIOS.md) using a more declarative syntax. Based on the behavior driven development (BDD) syntax of libraries such as [Jasmine](https://jasmine.github.io/) and [Mocha](https://mochajs.org/), parrot-friendly provides `describe`, `it`, and other methods to construct your scenarios object.
6 |
7 | ## Example
8 |
9 | ```js
10 | import { describe, it, get } from 'parrot-friendly';
11 |
12 | const scenarios = describe('Ship Log', () => {
13 | it('should display the ship log', () => {
14 | get('/ship_log')
15 | .response(require('./mocks/shipLog.json'))
16 | .delay(1200);
17 | });
18 |
19 | it('should show an error', () => {
20 | get('/ship_log').status(500);
21 | });
22 | });
23 |
24 | export default scenarios;
25 | ```
26 |
27 | ## API
28 |
29 | ### `describe(description, scenarioDefinitions)`
30 |
31 | Returns a scenarios object based on the `scenarioDefinitions` declared.
32 |
33 | #### Arguments
34 |
35 | - `description` (_String_): Scenarios object description. Currently this is not used internally but supported to provide a more common API.
36 | - `scenarioDefinitions` (_Function_): Function that will define scenarios when invoked.
37 |
38 | ### `it(description, mockDefinitions)`
39 |
40 | Adds a scenario with key `description` to the scenarios object.
41 |
42 | #### Arugments
43 |
44 | - `description` (_String_): Scenario description that will be used as a key to identify the scenario. Must be unique to a scenarios object.
45 | - `mockDefinitions`: (_Function_): Function that will define mock objects when invoked.
46 |
47 | ### `mock(mockDefinition)`
48 |
49 | Creates a mock for a HTTP request where `mockDefinition` is the entire mock object. This can be used in place of chaining methods such as `query` and `delay`, or to provide custom mock handling with a function.
50 |
51 | #### Arguments
52 |
53 | - `mockDefinition` (_Object_ or _Function_): Mock object with `request` and `response` keys or mock function.
54 |
55 | ### `request(requestDefinition)`
56 |
57 | Creates a mock for a HTTP request where `requestDefinition` is the entire request object. Can be used in place of chaining request methods such as `query` or to provide a custom matching function.
58 |
59 | #### Arguments
60 |
61 | - `requestDefinition` (_Object_ or _Function_): Request object to be matched against or request function returning true for a match and false for a miss.
62 |
63 | ### `METHOD(path)`
64 |
65 | Creates a mock for a HTTP request where METHOD is one of `get`, `head`, `post`, `put`, `del`, `connect`, `options`, `patch`.
66 |
67 | `del` is used in place of `delete` as `delete` is a JavaScript reserved word.
68 |
69 | #### Arguments
70 |
71 | - `path` (_String_): Path matcher string. May include route params.
72 |
73 | #### Methods
74 |
75 | ##### `.query(query)`
76 |
77 | Matches against the `query` object provided.
78 |
79 | ##### `.headers(headers)`
80 |
81 | Matches against the `headers` object provided
82 |
83 | ##### `.response(resource)`
84 |
85 | Responds with the `resource` provided.
86 |
87 | ##### `.delay(amount)`
88 |
89 | Delays the response for `amount` of milliseconds.
90 |
91 | ##### `.status(code)`
92 |
93 | Responds with a `code` status code.
94 |
95 | ### `graphql(path, schema, mocks)`
96 |
97 | Creates a mock for your GraphQL endpoint.
98 |
99 | #### Arguments
100 |
101 | - `path` (_String_): Path of your GraphQL endpoint.
102 | - `schema` (_String_): GraphQL schema string.
103 | - `mocks` (_Object_): Object describing your [mocking logic](https://www.apollographql.com/docs/graphql-tools/mocking.html#Customizing-mocks) that is passed to [graphql-tools](https://github.com/apollographql/graphql-tools) `mockServer`.
104 |
--------------------------------------------------------------------------------
/packages/parrot-graphql/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "parrot-graphql",
3 | "version": "5.3.0",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "parrot-graphql",
9 | "version": "5.0.0-alpha.1",
10 | "license": "Apache-2.0",
11 | "dependencies": {
12 | "@graphql-tools/mock": "^8.7.20",
13 | "graphql": "^15.8.0"
14 | }
15 | },
16 | "node_modules/@graphql-tools/merge": {
17 | "version": "8.4.2",
18 | "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-8.4.2.tgz",
19 | "integrity": "sha512-XbrHAaj8yDuINph+sAfuq3QCZ/tKblrTLOpirK0+CAgNlZUCHs0Fa+xtMUURgwCVThLle1AF7svJCxFizygLsw==",
20 | "dependencies": {
21 | "@graphql-tools/utils": "^9.2.1",
22 | "tslib": "^2.4.0"
23 | },
24 | "peerDependencies": {
25 | "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
26 | }
27 | },
28 | "node_modules/@graphql-tools/mock": {
29 | "version": "8.7.20",
30 | "resolved": "https://registry.npmjs.org/@graphql-tools/mock/-/mock-8.7.20.tgz",
31 | "integrity": "sha512-ljcHSJWjC/ZyzpXd5cfNhPI7YljRVvabKHPzKjEs5ElxWu2cdlLGvyNYepApXDsM/OJG/2xuhGM+9GWu5gEAPQ==",
32 | "dependencies": {
33 | "@graphql-tools/schema": "^9.0.18",
34 | "@graphql-tools/utils": "^9.2.1",
35 | "fast-json-stable-stringify": "^2.1.0",
36 | "tslib": "^2.4.0"
37 | },
38 | "peerDependencies": {
39 | "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
40 | }
41 | },
42 | "node_modules/@graphql-tools/schema": {
43 | "version": "9.0.19",
44 | "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-9.0.19.tgz",
45 | "integrity": "sha512-oBRPoNBtCkk0zbUsyP4GaIzCt8C0aCI4ycIRUL67KK5pOHljKLBBtGT+Jr6hkzA74C8Gco8bpZPe7aWFjiaK2w==",
46 | "dependencies": {
47 | "@graphql-tools/merge": "^8.4.1",
48 | "@graphql-tools/utils": "^9.2.1",
49 | "tslib": "^2.4.0",
50 | "value-or-promise": "^1.0.12"
51 | },
52 | "peerDependencies": {
53 | "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
54 | }
55 | },
56 | "node_modules/@graphql-tools/utils": {
57 | "version": "9.2.1",
58 | "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-9.2.1.tgz",
59 | "integrity": "sha512-WUw506Ql6xzmOORlriNrD6Ugx+HjVgYxt9KCXD9mHAak+eaXSwuGGPyE60hy9xaDEoXKBsG7SkG69ybitaVl6A==",
60 | "dependencies": {
61 | "@graphql-typed-document-node/core": "^3.1.1",
62 | "tslib": "^2.4.0"
63 | },
64 | "peerDependencies": {
65 | "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
66 | }
67 | },
68 | "node_modules/@graphql-typed-document-node/core": {
69 | "version": "3.2.0",
70 | "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz",
71 | "integrity": "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==",
72 | "peerDependencies": {
73 | "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
74 | }
75 | },
76 | "node_modules/fast-json-stable-stringify": {
77 | "version": "2.1.0",
78 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
79 | "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
80 | },
81 | "node_modules/graphql": {
82 | "version": "15.8.0",
83 | "resolved": "https://registry.npmjs.org/graphql/-/graphql-15.8.0.tgz",
84 | "integrity": "sha512-5gghUc24tP9HRznNpV2+FIoq3xKkj5dTQqf4v0CpdPbFVwFkWoxOM+o+2OC9ZSvjEMTjfmG9QT+gcvggTwW1zw==",
85 | "engines": {
86 | "node": ">= 10.x"
87 | }
88 | },
89 | "node_modules/tslib": {
90 | "version": "2.6.1",
91 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz",
92 | "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig=="
93 | },
94 | "node_modules/value-or-promise": {
95 | "version": "1.0.12",
96 | "resolved": "https://registry.npmjs.org/value-or-promise/-/value-or-promise-1.0.12.tgz",
97 | "integrity": "sha512-Z6Uz+TYwEqE7ZN50gwn+1LCVo9ZVrpxRPOhOLnncYkY1ZzOYtrX8Fwf/rFktZ8R5mJms6EZf5TqNOMeZmnPq9Q==",
98 | "engines": {
99 | "node": ">=12"
100 | }
101 | }
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/packages/parrot-middleware/__tests__/ParrotMiddleware.spec.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018 American Express Travel Related Services Company, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | import ParrotMiddleware from '../src/ParrotMiddleware';
16 |
17 | jest.mock(
18 | 'parrot-core',
19 | () =>
20 | class {
21 | constructor() {
22 | this.logger = { warn: jest.fn() };
23 | }
24 | }
25 | );
26 |
27 | describe('ParrotMiddleware', () => {
28 | it('should normalize', () => {
29 | const parrotFetch = new ParrotMiddleware();
30 | const normalized = parrotFetch.normalizeRequest({
31 | path: 'http://www.parrot.com/squawk?ahoy=matey',
32 | });
33 | expect(normalized).toMatchObject({
34 | path: 'http://www.parrot.com/squawk?ahoy=matey',
35 | });
36 | });
37 |
38 | it('should call next middleware and log a warning', () => {
39 | const req = {};
40 | const res = { headersSent: false };
41 | const next = jest.fn();
42 | const parrotMiddleware = new ParrotMiddleware();
43 | parrotMiddleware.resolver(req, res, next)();
44 | expect(next).toHaveBeenCalled();
45 | expect(parrotMiddleware.logger.warn).toHaveBeenCalledWith(
46 | 'No matching mock found for request',
47 | req.path
48 | );
49 | });
50 |
51 | it('should not call next middleware if headers sent', () => {
52 | const req = {};
53 | const res = { headersSent: true };
54 | const next = jest.fn();
55 | const parrotMiddleware = new ParrotMiddleware();
56 | parrotMiddleware.resolver(req, res, next)();
57 | expect(next).not.toHaveBeenCalled();
58 | });
59 |
60 | it('should send json if response is an object', () => {
61 | const req = {};
62 | const res = { json: jest.fn(), status: jest.fn() };
63 | const next = jest.fn();
64 | const response = { body: {} };
65 | const parrotMiddleware = new ParrotMiddleware();
66 | parrotMiddleware.resolver(req, res, next)(response);
67 | expect(res.json).toHaveBeenCalled();
68 | });
69 |
70 | it('should send status if there is no body', () => {
71 | const req = {};
72 | const res = { sendStatus: jest.fn(), status: jest.fn() };
73 | const next = jest.fn();
74 | const response = { status: 200 };
75 | const parrotMiddleware = new ParrotMiddleware();
76 | parrotMiddleware.resolver(req, res, next)(response);
77 | expect(res.sendStatus).toHaveBeenCalled();
78 | });
79 |
80 | it('should send body', () => {
81 | const req = {};
82 | const res = { send: jest.fn(), status: jest.fn() };
83 | const next = jest.fn();
84 | const response = { body: 'squawk' };
85 | const parrotMiddleware = new ParrotMiddleware();
86 | parrotMiddleware.resolver(req, res, next)(response);
87 | expect(res.send).toHaveBeenCalled();
88 | });
89 |
90 | it('should send body with type', () => {
91 | const req = {};
92 | const res = { send: jest.fn(), status: jest.fn(), type: jest.fn() };
93 | const next = jest.fn();
94 | const response = { body: '', contentType: 'html' };
95 | const parrotMiddleware = new ParrotMiddleware();
96 | parrotMiddleware.resolver(req, res, next)(response);
97 | expect(res.type).toHaveBeenCalled();
98 | expect(res.send).toHaveBeenCalled();
99 | });
100 |
101 | it('should return the active scenario override from the cookie', () => {
102 | const testScenarioName = 'Test Scenario Name';
103 | const req = {
104 | cookies: {
105 | parrotScenarioOverride: testScenarioName,
106 | },
107 | };
108 | const parrotMiddleware = new ParrotMiddleware();
109 | expect(parrotMiddleware.getActiveScenarioOverride(req)).toEqual(testScenarioName);
110 | });
111 |
112 | it('should return undefined if the active scenario override is not present as a cookie', () => {
113 | const req = {
114 | cookies: {},
115 | };
116 | const parrotMiddleware = new ParrotMiddleware();
117 | expect(parrotMiddleware.getActiveScenarioOverride(req)).toBeUndefined();
118 | });
119 |
120 | it('should return undefined if there are no cookies', () => {
121 | const req = {};
122 | const parrotMiddleware = new ParrotMiddleware();
123 | expect(parrotMiddleware.getActiveScenarioOverride(req)).toBeUndefined();
124 | });
125 | });
126 |
--------------------------------------------------------------------------------
/SCENARIOS.md:
--------------------------------------------------------------------------------
1 | # Scenarios
2 |
3 | Parrot scenarios describe combinations of data that you want to develop against. A scenario is comprised of multiple mocks that each describe how to match against an incoming request and what response to return.
4 |
5 | ## Examples
6 |
7 | Below are some examples of how to configure your Parrot scenarios using different methods.
8 | ### Response as an object
9 |
10 | ```js
11 | const scenarios = {
12 | 'has a ship log': [
13 | {
14 | request: '/ship_log', // If request is a string, the HTTP method defaults to GET
15 | response: {
16 | body: require('./mocks/shipLog.json'),
17 | delay: 1200, // Optional, defaults to 0
18 | status: 200, // Optional, defaults to 200
19 | },
20 | },
21 | ],
22 | };
23 | ```
24 |
25 | ### Response as a function
26 |
27 | ```js
28 | const scenarios = {
29 | 'has a ship log': [
30 | {
31 | request: '/ship_log',
32 | // Whatever is returned by this function will be put in the body of the response
33 | // Status will always be 200 for functions
34 | response: () => {
35 | return require('./mocks/shipLog.json');
36 | },
37 | },
38 | ],
39 | };
40 | ```
41 |
42 | ## Functional Scenarios
43 |
44 | ```js
45 | const scenarios = {
46 | 'has one ship': [
47 | // Instead of an object with request/response, the function will
48 | // handle all requests. Use the `match` function to match routes.
49 | // Use this to set dynamic HTTP status codes.
50 | (req, match) => {
51 | if (match({ path: '/ship_log' })) {
52 | return {
53 | status: 200,
54 | body: require('./mocks/shipLog.json'),
55 | delay: 100,
56 | };
57 | }
58 | return {
59 | status: 404,
60 | body: { error: 'Resource Not Found' },
61 | delay: 100,
62 | };
63 | },
64 | ],
65 | };
66 | ```
67 |
68 | ### GraphQL Example
69 |
70 | ```js
71 | import graphql from 'parrot-graphql';
72 | import schema from './schema'; // our GraphQL schema
73 |
74 | const scenarios = {
75 | 'has a ship log from GraphQL': [
76 | graphql('/graphql', schema, () => ({
77 | ShipLog: () => require('./mocks/shipLog.json'),
78 | })),
79 | ],
80 | };
81 | ```
82 |
83 | ## Scenarios API
84 |
85 | The options you can define on the `request` and `response` objects are described below.
86 |
87 | ### Request Options
88 |
89 | | Property | Description | Type |
90 | | --------- | ------------------------------------------------------------ | ------ |
91 | | `path` | Path matcher string. May include route params | String |
92 | | `query` | Match against querystring key/value pairs | Object |
93 | | `method` | HTTP method defaults to GET, explicitly specify to handle others | String |
94 | | `headers` | HTTP headers to match against | Object |
95 |
96 | ### Response Options
97 |
98 | | Property | Description | Type |
99 | | -------- | ------------------------------------------------------------ | -------------------------- |
100 | | `body` | Blob to be returned directly or object to be returned as JSON or function that resolve to a blob or object | String / Object / Function |
101 | | `delay` | Set a response delay in milliseconds | Number |
102 | | `status` | Defaults to 200, but can be set explicitly | Number |
103 |
104 | ## Request, Response, and Mock Functions
105 |
106 | In addition to defining `request`, `response` and `mock` as objects, they can also be defined as functions.
107 |
108 | ### `request(req, match)`
109 |
110 | If `request` is a function, it will be run when determing if an incoming request matches the mock. If a truthy value is returned the mock will match, if a falsy value is returned the mock will not match.
111 |
112 | #### Arguments
113 |
114 | * `req`: (*Object*): The normalized request object with `path`, `method`, `body`, `headers`, and `query` properties.
115 | * `match`: (*Function*): Parrot's internal matching function that can be run against a request object.
116 |
117 | ### `response(req)`
118 |
119 | If `response` is a function, it will be run once the request has matched and the response is being resolved. The value that is returned from the function will be sent as the response.
120 |
121 | #### Arguments
122 |
123 | * `req`: (*Object*): The normalized request object with `path`, `method`, `body`, `headers`, `query`, and `params` properties.
124 | * Additional implementation-specific arguments passed are the Express `req` and `res` objects for parrot-middleware and the `input` and `init` arguments to `fetch` for parrot-fetch.
125 |
126 | ### `mock(req, match)`
127 |
128 | If the entire `mock` is a function, it will be called when determining if an incoming request matches the mock. The value that is returned from the function will be sent as the response.
129 |
130 | #### Arguments
131 |
132 | - `req`: (*Object*): The normalized request object with `path`, `method`, `body`, `headers`, and `query` properties.
133 | - `match`: (*Function*): Parrot's internal matching function that can be run against a request object.
134 | - Additional implementation-specific arguments passed are the Express `req` and `res` objects for parrot-middleware and the `input` and `init` arguments to `fetch` for parrot-fetch.
135 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | [Parrot is now InnerSource](https://github.com/americanexpress/one-app/issues/1393)
4 |
5 |
6 |
7 |
8 |
9 |
10 | Parrot is a set of tools that allow you to create HTTP mocks and organize them into scenarios in order to develop your app against different sets of data. We have implemented all of Parrot's functionality in JavaScript, but [scenarios](https://github.com/americanexpress/parrot/blob/main/SCENARIOS.md) are a general specification that can be implemented in any language.
11 |
12 | ## 🤹 Usage
13 |
14 | Let's walk through a common development workflow using Parrot.
15 |
16 | #### Define your [scenarios](https://github.com/americanexpress/parrot/blob/main/SCENARIOS.md)
17 |
18 | ```js
19 | import { describe, it, get, post, graphql } from 'parrot-friendly';
20 | import casual from 'casual'; // for generating fake data
21 | import schema from './schema'; // our GraphQL schema
22 |
23 | const scenarios = describe('Ship Log', () => {
24 | it('has a ship log', () => {
25 | // respond with a mock JSON file and add a delay
26 | get('/ship_log')
27 | .response(require('./mocks/shipLog.json'))
28 | .delay(1200);
29 |
30 | // respond with the request body that was sent
31 | post('/ship_log').response(req => req.body);
32 | });
33 |
34 | it('has a random ship log', () => {
35 | // respond with random data generated by casual
36 | get('/ship_log').response(() => [
37 | {
38 | port: casual.city,
39 | captain: casual.full_name,
40 | },
41 | ]);
42 | });
43 |
44 | it('has a server error', () => {
45 | // respond with a 500 status
46 | get('/ship_log').status(500);
47 | });
48 |
49 | it('has a ship log from GraphQL', () => {
50 | // respond to GraphQL queries
51 | graphql('/graphql', schema, () => ({
52 | ShipLog: () => require('./mocks/shipLog.json'),
53 | }));
54 | });
55 | });
56 |
57 | export default scenarios;
58 | ```
59 |
60 | More information about writing scenarios can be found in the [scenarios documentation](https://github.com/americanexpress/parrot/blob/main/SCENARIOS.md).
61 |
62 | #### Add them to your server
63 |
64 | ```js
65 | import express from 'express';
66 | import parrot from 'parrot-middleware';
67 | import scenarios from './scenarios';
68 |
69 | const app = express();
70 | app.use(parrot(scenarios));
71 | app.listen(3000);
72 | ```
73 |
74 | #### Develop with Parrot's [devtools](https://github.com/americanexpress/parrot/blob/main/packages/parrot-devtools)
75 |
76 | 
77 |
78 | #### Example API requests
79 |
80 | Fetch current scenario.
81 |
82 | ```sh
83 | $ curl 'http://localhost:3002/parrot/scenario'
84 | ```
85 |
86 | Fetch all scenarios.
87 |
88 | ```sh
89 | $ curl 'http://localhost:3002/parrot/scenarios'
90 | ```
91 |
92 | Setting parrot to a new scenario.
93 |
94 | ```sh
95 | $ curl -X POST -H "Content-Type: application/json" -d '{ "scenario": "[scenario name here]" }' 'http://localhost:3002/parrot/scenario'
96 | ```
97 |
98 | ## 📦 Packages
99 |
100 | Parrot is divided into several packages that can be used together depending on your use case.
101 |
102 | | Name | Description |
103 | | ------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
104 | | **[parrot-core](https://github.com/americanexpress/parrot/blob/main/packages/parrot-core)** | Core Parrot functionality that can be extended to new use cases |
105 | | **[parrot-devtools](https://github.com/americanexpress/parrot/blob/main/packages/parrot-devtools)** | Devtools that allow you to switch between Parrot scenarios |
106 | | **[parrot-fetch](https://github.com/americanexpress/parrot/blob/main/packages/parrot-fetch)** | Fetch mocking implementation of Parrot |
107 | | **[parrot-friendly](https://github.com/americanexpress/parrot/blob/main/packages/parrot-friendly)** | Helper library to write your scenarios in BDD style |
108 | | **[parrot-graphql](https://github.com/americanexpress/parrot/blob/main/packages/parrot-graphql)** | Helper library to add GraphQL mocks to your scenarios |
109 | | **[parrot-middleware](https://github.com/americanexpress/parrot/blob/main/packages/parrot-middleware)** | Express middleware implementation of Parrot |
110 | | **[parrot-server](https://github.com/americanexpress/parrot/blob/main/packages/parrot-server)** | CLI to get a parrot server up and running |
111 |
112 | ## 🏆 Contributing
113 |
114 | We welcome Your interest in the American Express Open Source Community on Github.
115 | Any Contributor to any Open Source Project managed by the American Express Open
116 | Source Community must accept and sign an Agreement indicating agreement to the
117 | terms below. Except for the rights granted in this Agreement to American Express
118 | and to recipients of software distributed by American Express, You reserve all
119 | right, title, and interest, if any, in and to Your Contributions. Please [fill out the Agreement](https://cla-assistant.io/americanexpress/parrot).
120 |
121 | Please see our [CONTRIBUTING.md](./CONTRIBUTING.md).
122 |
123 | ## 🗝️ License
124 |
125 | Any contributions made under this project will be governed by the [Apache License 2.0](./LICENSE.md).
126 |
127 | ## 🗣️ Code of Conduct
128 |
129 | This project adheres to the [American Express Community Guidelines](./CODE_OF_CONDUCT.md).
130 | By participating, you are expected to honor these guidelines.
131 |
--------------------------------------------------------------------------------