Skip to content
This repository was archived by the owner on May 8, 2023. It is now read-only.

Commit 78fef2b

Browse files
authored
Edit fixture button (#942)
1 parent cf7acd4 commit 78fef2b

File tree

24 files changed

+279
-61
lines changed

24 files changed

+279
-61
lines changed

packages/react-cosmos-playground2/src/global/registerPlugins.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ require('../plugins/Nav').register();
1818
require('../plugins/ContentOverlay').register();
1919
require('../plugins/ControlPanel').register();
2020
require('../plugins/ResponsivePreview').register();
21+
require('../plugins/EditFixtureButton').register();
2122

2223
// TODO: Read list of disabled plugins from user config
2324
enablePlugin('controlPanel', false);
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import * as React from 'react';
2+
import { FixtureId } from 'react-cosmos-shared2/renderer';
3+
import { Button } from '../../shared/components';
4+
import { EditIcon } from '../../shared/icons';
5+
6+
export type EditFixtureButtonProps = {
7+
devServerOn: boolean;
8+
selectedFixtureId: FixtureId | null;
9+
};
10+
11+
export function EditFixtureButton({
12+
devServerOn,
13+
selectedFixtureId
14+
}: EditFixtureButtonProps) {
15+
if (!devServerOn || !selectedFixtureId) {
16+
return null;
17+
}
18+
19+
return (
20+
<Button
21+
icon={<EditIcon />}
22+
label="edit"
23+
onClick={() => openFile(selectedFixtureId.path)}
24+
/>
25+
);
26+
}
27+
28+
function openFile(filePath: string) {
29+
fetch(`/_open?filePath=${filePath}`, { credentials: 'same-origin' });
30+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import * as React from 'react';
2+
import { loadPlugins, Slot } from 'react-plugin';
3+
import {
4+
render,
5+
waitForElement,
6+
fireEvent,
7+
RenderResult
8+
} from 'react-testing-library';
9+
import { register } from '.';
10+
import { createArrayPlug } from '../../shared/slot';
11+
import { cleanup, mockPlug } from '../../testHelpers/plugin';
12+
import { mockCore, mockRouter } from '../../testHelpers/pluginMocks';
13+
import { mockFetch } from './testHelpers';
14+
15+
afterEach(cleanup);
16+
17+
function mockSelectedFixtureId() {
18+
mockRouter({
19+
getSelectedFixtureId: () => ({ path: 'foo.js', name: null })
20+
});
21+
}
22+
23+
function mockFixtureAction() {
24+
mockPlug({
25+
slotName: 'fixtureActions',
26+
render: createArrayPlug('fixtureActions', () => <>fooAction</>)
27+
});
28+
}
29+
30+
function waitForMockFixtureAction({ getByText }: RenderResult) {
31+
return waitForElement(() => getByText('fooAction'));
32+
}
33+
34+
async function loadTestPlugins() {
35+
mockSelectedFixtureId();
36+
mockFixtureAction();
37+
register();
38+
loadPlugins();
39+
const renderer = render(<Slot name="fixtureActions" />);
40+
await waitForMockFixtureAction(renderer);
41+
return renderer;
42+
}
43+
44+
it(`doesn't render button when dev server is off`, async () => {
45+
mockCore({
46+
isDevServerOn: () => false
47+
});
48+
const { queryByText } = await loadTestPlugins();
49+
expect(queryByText(/edit/i)).toBeNull();
50+
});
51+
52+
it('renders button', async () => {
53+
mockCore({
54+
isDevServerOn: () => true
55+
});
56+
const { getByText } = await loadTestPlugins();
57+
await waitForElement(() => getByText(/edit/i));
58+
});
59+
60+
it('calls server endpoint on button click', async () => {
61+
await mockFetch(async fetchMock => {
62+
mockCore({
63+
isDevServerOn: () => true
64+
});
65+
const { getByText } = await loadTestPlugins();
66+
67+
const editBtn = await waitForElement(() => getByText(/edit/i));
68+
fireEvent.click(editBtn);
69+
70+
const openFileUrl = '/_open?filePath=foo.js';
71+
expect(fetchMock).toBeCalledWith(openFileUrl, expect.any(Object));
72+
});
73+
});
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { createPlugin } from 'react-plugin';
2+
import { createArrayPlug } from '../../shared/slot';
3+
import { getMethodsOf } from '../../testHelpers/plugin';
4+
import { CoreSpec } from './../Core/public';
5+
import { RouterSpec } from './../Router/public';
6+
import { EditFixtureButton, EditFixtureButtonProps } from './EditFixtureButton';
7+
import { EditFixtureButtonSpec } from './public';
8+
9+
const { plug, register } = createPlugin<EditFixtureButtonSpec>({
10+
name: 'editFixtureButton'
11+
});
12+
13+
plug({
14+
slotName: 'fixtureActions',
15+
render: createArrayPlug<EditFixtureButtonProps>(
16+
'fixtureActions',
17+
EditFixtureButton
18+
),
19+
getProps: () => {
20+
const core = getMethodsOf<CoreSpec>('core');
21+
const router = getMethodsOf<RouterSpec>('router');
22+
return {
23+
devServerOn: core.isDevServerOn(),
24+
selectedFixtureId: router.getSelectedFixtureId()
25+
};
26+
}
27+
});
28+
29+
export { register };
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export type EditFixtureButtonSpec = {
2+
name: 'editFixtureButton';
3+
};
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export async function mockFetch(
2+
cb: (fetchMock: jest.Mock) => Promise<unknown>
3+
) {
4+
const w = window as any;
5+
const origFetch = window.fetch;
6+
w.fetch = jest.fn();
7+
await cb(w.fetch);
8+
w.fetch = origFetch;
9+
}

packages/react-cosmos-playground2/src/plugins/Nav/index.test.tsx

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as React from 'react';
2-
import delay from 'delay';
2+
import retry from '@skidding/async-retry';
33
import { render, waitForElement, fireEvent } from 'react-testing-library';
44
import { Slot, loadPlugins, MethodHandlers } from 'react-plugin';
55
import { cleanup, mockMethodsOf } from '../../testHelpers/plugin';
@@ -36,10 +36,11 @@ function mockRouter(methods: Partial<MethodHandlers<RouterSpec>> = {}) {
3636
mockMethodsOf<RouterSpec>('router', methods);
3737
}
3838

39-
function loadTestPlugins() {
39+
async function loadTestPlugins() {
4040
loadPlugins();
41-
42-
return render(<Slot name="left" />);
41+
const renderer = render(<Slot name="left">replace me</Slot>);
42+
await retry(() => expect(renderer.queryByText('replace me')).toBeNull());
43+
return renderer;
4344
}
4445

4546
it('renders fixture list from renderer state', async () => {
@@ -48,8 +49,7 @@ it('renders fixture list from renderer state', async () => {
4849
getSelectedFixtureId: () => null,
4950
isFullScreen: () => false
5051
});
51-
const { getByText } = loadTestPlugins();
52-
52+
const { getByText } = await loadTestPlugins();
5353
await waitForElement(() => getByText(/ein/i));
5454
await waitForElement(() => getByText(/zwei/i));
5555
await waitForElement(() => getByText(/drei/i));
@@ -65,7 +65,7 @@ it('sends fixtureId to router on fixture click', async () => {
6565
selectFixture
6666
});
6767

68-
const { getByText } = loadTestPlugins();
68+
const { getByText } = await loadTestPlugins();
6969
fireEvent.click(getByText(/zwei/i));
7070

7171
expect(selectFixture).toBeCalledWith(
@@ -83,8 +83,7 @@ it('renders nav element', async () => {
8383
getSelectedFixtureId: () => null,
8484
isFullScreen: () => false
8585
});
86-
const { getByTestId } = loadTestPlugins();
87-
86+
const { getByTestId } = await loadTestPlugins();
8887
await waitForElement(() => getByTestId('nav'));
8988
});
9089

@@ -94,10 +93,6 @@ it('does not render nav element in full screen mode', async () => {
9493
getSelectedFixtureId: () => ({ path: 'zwei.js', name: null }),
9594
isFullScreen: () => true
9695
});
97-
const { queryByTestId } = loadTestPlugins();
98-
99-
// Make sure the nav element doesn't appear async in the next event loops
100-
await delay(100);
101-
96+
const { queryByTestId } = await loadTestPlugins();
10297
expect(queryByTestId('nav')).toBeNull();
10398
});

packages/react-cosmos-playground2/src/plugins/RendererHeader/RendererHeader.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ type Props = {
1919
unselectFixture: () => void;
2020
};
2121

22-
// TODO: Improve UX of refresh button, which seems like it's not doing anything
22+
// TODO: Improve UX of refresh button, which can seem like it's not doing anything
2323
export function RendererHeader({
2424
selectedFixtureId,
2525
fullScreen,
@@ -49,7 +49,7 @@ export function RendererHeader({
4949
<Message>No fixture selected</Message>
5050
</Left>
5151
<Right>
52-
<Slot name="fixtureActions" />
52+
<Slot name="rendererActions" />
5353
<Button disabled icon={<MaximizeIcon />} label="fullscreen" />
5454
</Right>
5555
</Container>
@@ -68,7 +68,7 @@ export function RendererHeader({
6868
/>
6969
</Left>
7070
<Right>
71-
<Slot name="fixtureActions" />
71+
<Slot name="rendererActions" />
7272
<Button disabled icon={<MaximizeIcon />} label="fullscreen" />
7373
</Right>
7474
</Container>
@@ -88,9 +88,10 @@ export function RendererHeader({
8888
label="refresh"
8989
onClick={() => selectFixture(selectedFixtureId, false)}
9090
/>
91+
<Slot name="fixtureActions" />
9192
</Left>
9293
<Right>
93-
<Slot name="fixtureActions" />
94+
<Slot name="rendererActions" />
9495
<Button
9596
icon={<MaximizeIcon />}
9697
label="fullscreen"
Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as React from 'react';
2-
import delay from 'delay';
32
import { render } from 'react-testing-library';
3+
import retry from '@skidding/async-retry';
44
import { Slot, loadPlugins } from 'react-plugin';
55
import { cleanup, mockMethodsOf, mockPlug } from '../../../testHelpers/plugin';
66
import { RouterSpec } from '../../Router/public';
@@ -21,28 +21,39 @@ function registerTestPlugins() {
2121
});
2222
}
2323

24-
function loadTestPlugins() {
24+
async function loadTestPlugins() {
2525
loadPlugins();
26+
const renderer = render(<Slot name="rendererHeader">replace me</Slot>);
27+
await retry(() => expect(renderer.queryByText('replace me')).toBeNull());
28+
return renderer;
29+
}
30+
31+
const RENDERER_ACTION = 'foo action';
32+
function mockRendererAction() {
33+
mockPlug({ slotName: 'rendererActions', render: RENDERER_ACTION });
34+
}
2635

27-
return render(<Slot name="rendererHeader" />);
36+
const FIXTURE_ACTION = 'bar action';
37+
function mockFixtureAction() {
38+
mockPlug({ slotName: 'fixtureActions', render: FIXTURE_ACTION });
2839
}
2940

3041
it('does not render close button', async () => {
3142
registerTestPlugins();
32-
mockPlug({ slotName: 'fixtureActions', render: 'pluggable actions' });
33-
const { queryByText } = loadTestPlugins();
34-
35-
// Make sure the element doesn't appear async in the next event loops
36-
await delay(100);
43+
const { queryByText } = await loadTestPlugins();
3744
expect(queryByText(/close/i)).toBeNull();
3845
});
3946

40-
it('does not render "fixtureActions" slot', async () => {
47+
it('does not render renderer actions', async () => {
4148
registerTestPlugins();
42-
mockPlug({ slotName: 'fixtureActions', render: 'pluggable actions' });
43-
const { queryByText } = loadTestPlugins();
49+
mockRendererAction();
50+
const { queryByText } = await loadTestPlugins();
51+
expect(queryByText(RENDERER_ACTION)).toBeNull();
52+
});
4453

45-
// Make sure the element doesn't appear async in the next event loops
46-
await delay(100);
47-
expect(queryByText(/pluggable actions/i)).toBeNull();
54+
it('does not render fixture actions', async () => {
55+
registerTestPlugins();
56+
mockFixtureAction();
57+
const { queryByText } = await loadTestPlugins();
58+
expect(queryByText(FIXTURE_ACTION)).toBeNull();
4859
});

packages/react-cosmos-playground2/src/plugins/RendererHeader/__tests__/missingFixture.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,12 @@ function registerTestPlugins(unselectFixture = () => {}) {
2323

2424
function loadTestPlugins() {
2525
loadPlugins();
26-
2726
return render(<Slot name="rendererHeader" />);
2827
}
2928

3029
it('renders missing state message', async () => {
3130
registerTestPlugins();
3231
const { getByText } = loadTestPlugins();
33-
3432
await waitForElement(() => getByText(/fixture not found/i));
3533
});
3634

@@ -47,6 +45,5 @@ it('renders home button', async () => {
4745
it('renders disabled fullscreen button', async () => {
4846
registerTestPlugins();
4947
const { getByText } = loadTestPlugins();
50-
5148
expect(getByText(/fullscreen/i)).toHaveAttribute('disabled');
5249
});

0 commit comments

Comments
 (0)