Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions e2e/editor-sidepanel.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,12 +181,9 @@ test('a side panel that shows the editor stays the chosen position', async ({
await extension.evaluate(async () => {
const items = await chrome.storage.local.get([
'options',
'sidePanelIgnoresTabs',
'chromiumVariant',
]);
return [
items['sidePanelIgnoresTabs'],
items['options'].layout.dockLocation,
];
return [items['chromiumVariant'], items['options'].layout.dockLocation];
})
).toEqual([undefined, 'sidepanel']);
expect(await isSidePanelOpen(extension)).toBe(true);
Expand Down
22 changes: 20 additions & 2 deletions src/apps/background/global-commands.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ jest.mock('./restore-tabs', () => ({
restoreTab: jest.fn(async () => undefined),
}));
jest.mock('./side-panel-check', () => ({
canUseEditorSidePanel: () => !!chrome.sidePanel,
opensEditorInSidePanel: () => !!chrome.sidePanel && mockDockedInSidePanel,
onEditorSidePanelOpened: jest.fn(),
}));
jest.mock('./styles', () => ({
Expand All @@ -16,6 +16,8 @@ import { restoreTab } from './restore-tabs';
import { applyStylesToAllTabs, toggleForPage } from './styles';
import { handleCommand } from './global-commands';

let mockDockedInSidePanel = true;

const PAGE = { id: 7, url: 'https://example.com/' } as chrome.tabs.Tab;

/**
Expand Down Expand Up @@ -68,6 +70,7 @@ const flush = () => new Promise(resolve => setTimeout(resolve));
describe('handleCommand', () => {
beforeEach(() => {
jest.clearAllMocks();
mockDockedInSidePanel = true;
});

it('asks the page to carry out a shortcut that acts on it', async () => {
Expand Down Expand Up @@ -106,7 +109,7 @@ describe('handleCommand', () => {
expect(api.tabs.sendMessage).not.toBeCalled();
});

it('leaves the editor to the page where the side panel isn’t the dock', async () => {
it('leaves the editor to the page where the side panel can’t open', async () => {
const api = makeChrome({ panelSetUp: false });

handleCommand('stylebot', PAGE);
Expand All @@ -119,6 +122,21 @@ describe('handleCommand', () => {
);
});

it('leaves the editor to the page where the side panel isn’t the dock', async () => {
mockDockedInSidePanel = false;
const api = makeChrome({ panelSetUp: true });

handleCommand('stylebot', PAGE);
await flush();

expect(api.sidePanel?.open).not.toBeCalled();
expect(api.tabs.sendMessage).toBeCalledWith(
7,
{ name: 'RunCommand', command: 'stylebot' },
expect.any(Function)
);
});

it('leaves the editor to the page in a browser without a side panel', async () => {
const api = makeChrome({ sidePanel: false });

Expand Down
6 changes: 3 additions & 3 deletions src/apps/background/global-commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import * as editorWindow from './editor-window';
import { restoreTab } from './restore-tabs';
import { applyStylesToAllTabs, toggleForPage } from './styles';
import {
canUseEditorSidePanel,
onEditorSidePanelOpened,
opensEditorInSidePanel,
} from './side-panel-check';

const isCommandName = (command: string): command is StylebotCommandName =>
Expand Down Expand Up @@ -59,7 +59,7 @@ const runInPage = (tabId: number, command: StylebotCommandName): void => {
/**
* Toggles the editor where the side panel is the dock. The panel is set up
* on every tab ahead of time then, so it opens here, before anything is
* awaited and the shortcut's gesture lapses; elsewhere the open fails and
* awaited and the shortcut's gesture lapses. Where the open fails anyway,
* the page toggles its own editor.
*/
const toggleEditorWithSidePanel = (tabId: number): void => {
Expand Down Expand Up @@ -109,7 +109,7 @@ export const handleCommand = (
if (command === 'stylebot') {
if (target.fromEditorWindow) {
editorWindow.close(target.tabId);
} else if (canUseEditorSidePanel()) {
} else if (opensEditorInSidePanel()) {
toggleEditorWithSidePanel(target.tabId);
} else {
runInPage(target.tabId, command);
Expand Down
4 changes: 4 additions & 0 deletions src/apps/background/listeners.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ jest.mock('./restore-tabs', () => ({
}));
jest.mock('./badge', () => ({ updateBadge: jest.fn() }));
jest.mock('./options', () => ({ get: jest.fn(), pruneRetired: jest.fn() }));
jest.mock('./side-panel-check', () => ({
initSidePanelCheck: jest.fn(),
onEditorSidePanelOpened: jest.fn(),
}));
jest.mock('./sync-scheduler', () => ({
AWAY_SECONDS: 60,
isSyncAlarm: jest.fn(),
Expand Down
3 changes: 3 additions & 0 deletions src/apps/background/listeners.ts
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,9 @@ export const initListeners = (): void => {
case 'EditorSidePanelOpened':
onEditorSidePanelOpened(message.tabId);
break;
case 'RunPageCommand':
handleCommand(message.command, sender.tab);
break;
case 'RestoreTab':
restoreTab(message.tabId).then(() => sendResponse({ ok: true }));
break;
Expand Down
34 changes: 31 additions & 3 deletions src/apps/background/options.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,21 @@ beforeEach(() => {
storage: {
local: {
get: jest.fn(
(key: string) =>
(keys: string | Array<string>) =>
new Promise(resolve => {
// Simulate the read taking a tick, so a second set() call can
// start before the first one's read-modify-write finishes.
setTimeout(() => resolve({ [key]: store[key] }), 10);
setTimeout(
() =>
resolve(
Object.fromEntries(
([] as Array<string>)
.concat(keys)
.map(key => [key, store[key]])
)
),
10
);
})
),
set: jest.fn(
Expand Down Expand Up @@ -81,7 +91,25 @@ describe('getAll', () => {
expect((await getAll()).layout.dockLocation).toBe('right');
});

it('docks right where the browser ignores per-tab side panels', async () => {
it('docks right where the browser won’t show the side panel', async () => {
store = {
chromiumVariant: 'arc',
options: {
layout: {
width: 360,
adjustPageLayout: false,
dockLocation: 'sidepanel',
},
},
};
(chrome as unknown as { sidePanel: object }).sidePanel = {
open: jest.fn(),
};

expect((await getAll()).layout.dockLocation).toBe('right');
});

it('docks right in Vivaldi, as 4.0 recorded it', async () => {
store = {
sidePanelIgnoresTabs: true,
options: {
Expand Down
22 changes: 14 additions & 8 deletions src/apps/background/options.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type { StylebotOptions } from '@stylebot/types';
import { defaultOptions } from '@stylebot/settings';
import {
isSidePanelIgnoringTabs,
getChromiumVariant,
doesSidePanelWorkInChromiumVariant,
supportsEditorSidePanel,
} from '@stylebot/utils';

Expand All @@ -13,15 +14,20 @@ export const getAll = async (): Promise<StylebotOptions> => {
const items = await chrome.storage.local.get('options');
const options: StylebotOptions = { ...defaultOptions, ...items['options'] };

// Without a side panel the editor docks in the page, where it used to.
if (
options.layout.dockLocation === 'sidepanel' &&
(!supportsEditorSidePanel() || (await isSidePanelIgnoringTabs()))
) {
return { ...options, layout: { ...options.layout, dockLocation: 'right' } };
if (options.layout.dockLocation !== 'sidepanel') {
return options;
}

return options;
const variant = await getChromiumVariant();
const hasSidePanel =
supportsEditorSidePanel() && doesSidePanelWorkInChromiumVariant(variant);

if (hasSidePanel) {
return options;
}

// Without a side panel the editor docks in the page, where it used to.
return { ...options, layout: { ...options.layout, dockLocation: 'right' } };
};

export const get = async (
Expand Down
72 changes: 61 additions & 11 deletions src/apps/background/side-panel-check.test.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import {
isEditorSidePanelOpen,
isSidePanelIgnoringTabs,
} from '@stylebot/utils';
import { isEditorSidePanelOpen, getChromiumVariant } from '@stylebot/utils';

import { get as getOption, set as setOption } from './options';
import { onEditorSidePanelOpened } from './side-panel-check';
import {
initSidePanelCheck,
onEditorSidePanelOpened,
opensEditorInSidePanel,
} from './side-panel-check';

jest.mock('@stylebot/utils', () => ({
...jest.requireActual('@stylebot/utils'),
isEditorSidePanelOpen: jest.fn(),
isSidePanelIgnoringTabs: jest.fn(),
getChromiumVariant: jest.fn(),
supportsEditorSidePanel: () => true,
}));
jest.mock('./options', () => ({
Expand Down Expand Up @@ -41,7 +42,7 @@ beforeEach(() => {
jest.clearAllMocks();
(getOption as jest.Mock).mockResolvedValue(layout);
(isEditorSidePanelOpen as jest.Mock).mockResolvedValue(false);
(isSidePanelIgnoringTabs as jest.Mock).mockResolvedValue(false);
(getChromiumVariant as jest.Mock).mockResolvedValue(undefined);
global.chrome = {
tabs: { sendMessage: jest.fn() },
runtime: {},
Expand Down Expand Up @@ -70,9 +71,9 @@ describe('onEditorSidePanelOpened', () => {
});

it('docks the editor in the page once the panel reports it is hosted as a tab', async () => {
(isSidePanelIgnoringTabs as jest.Mock)
.mockResolvedValueOnce(false)
.mockResolvedValue(true);
(getChromiumVariant as jest.Mock)
.mockResolvedValueOnce(undefined)
.mockResolvedValue('vivaldi');

await check();

Expand All @@ -84,7 +85,7 @@ describe('onEditorSidePanelOpened', () => {
});

it('opens the editor in the page at once when the browser is already known', async () => {
(isSidePanelIgnoringTabs as jest.Mock).mockResolvedValue(true);
(getChromiumVariant as jest.Mock).mockResolvedValue('arc');
(getOption as jest.Mock).mockResolvedValue({
...layout,
dockLocation: 'right',
Expand All @@ -97,3 +98,52 @@ describe('onEditorSidePanelOpened', () => {
openStylebot();
});
});

describe('opensEditorInSidePanel', () => {
let onChanged: (
changes: Record<string, chrome.storage.StorageChange>,
area: string
) => void;

beforeEach(() => {
global.chrome = {
sidePanel: { open: jest.fn() },
storage: {
onChanged: {
addListener: jest.fn(listener => {
onChanged = listener;
}),
},
},
} as unknown as typeof chrome;
});

it('tries the side panel until the dock is read', () => {
jest.isolateModules(() => {
const check = jest.requireActual('./side-panel-check');
expect(check.opensEditorInSidePanel()).toBe(true);
});
});

it('follows the dock as it changes', async () => {
initSidePanelCheck();
await jest.runAllTimersAsync();
expect(opensEditorInSidePanel()).toBe(true);

onChanged(
{
options: { newValue: { layout: { ...layout, dockLocation: 'right' } } },
},
'local'
);
expect(opensEditorInSidePanel()).toBe(false);
});

it('stays in the page once the browser is found not to show it', async () => {
initSidePanelCheck();
await jest.runAllTimersAsync();

onChanged({ chromiumVariant: { newValue: 'arc' } }, 'local');
expect(opensEditorInSidePanel()).toBe(false);
});
});
Loading
Loading