506 lines
17 KiB
TypeScript
506 lines
17 KiB
TypeScript
import {
|
|
addStalkerPortal,
|
|
addXtreamPortal,
|
|
closeElectronApp,
|
|
createMutableTextServer,
|
|
deleteSource,
|
|
dragSourceBefore,
|
|
expect,
|
|
expectPlaylistUpdatedToast,
|
|
expectSourceDialogValues,
|
|
getVisibleSourceTitles,
|
|
importM3uPlaylistFromNativeDialog,
|
|
importM3uPlaylistFromUrl,
|
|
launchElectronApp,
|
|
openSources,
|
|
openSourceEditor,
|
|
refreshSource,
|
|
resetMockServers,
|
|
restartElectronApp,
|
|
saveSourceDialog,
|
|
selectSourceSort,
|
|
selectSourceTypeFilter,
|
|
sourceRowByTitle,
|
|
test,
|
|
updateSourceDialog,
|
|
waitForM3uCatalog,
|
|
waitForPortalDebugEvent,
|
|
waitForXtreamCatalog,
|
|
waitForSourceRowIdle,
|
|
writeTemporaryM3uFile,
|
|
} from './electron-test-fixtures';
|
|
|
|
const localSourceFileName = 'alpha-local-source.m3u';
|
|
const localSourceDisplayName = 'alpha-local-source';
|
|
const urlSourceFileName = 'omega-url-source.m3u';
|
|
const xtreamSourceName = 'Charlie Xtream Portal';
|
|
const stalkerSourceName = 'Bravo Stalker Portal';
|
|
const editableLocalSourceDisplayName = 'editable-local-source';
|
|
const deletableLocalSourceDisplayName = 'deletable-local-source';
|
|
const refreshLocalSourceDisplayName = 'refresh-local-source';
|
|
|
|
test.describe('Electron Sources View', () => {
|
|
test('filters and sorts sources, including persisted custom order', async ({
|
|
dataDir,
|
|
request,
|
|
}) => {
|
|
await resetMockServers(request, ['xtream', 'stalker']);
|
|
const localFilePath = writeTemporaryM3uFile(
|
|
dataDir,
|
|
localSourceFileName,
|
|
[
|
|
{
|
|
groupTitle: 'News',
|
|
name: 'Alpha Local News',
|
|
url: 'https://streams.example.test/local-alpha.m3u8',
|
|
},
|
|
]
|
|
);
|
|
const urlServer = await createMutableTextServer(
|
|
`#EXTM3U
|
|
#EXTINF:-1 group-title="Sports",Omega URL Sports
|
|
https://streams.example.test/url-omega.m3u8
|
|
`,
|
|
{
|
|
contentType: 'application/vnd.apple.mpegurl',
|
|
resourcePath: `/${urlSourceFileName}`,
|
|
}
|
|
);
|
|
|
|
const app = await launchElectronApp(dataDir);
|
|
|
|
try {
|
|
await importM3uPlaylistFromNativeDialog(app, localFilePath);
|
|
await importM3uPlaylistFromUrl(
|
|
app.mainWindow,
|
|
urlServer.resourceUrl
|
|
);
|
|
await addXtreamPortal(app.mainWindow, {
|
|
name: xtreamSourceName,
|
|
});
|
|
await addStalkerPortal(app.mainWindow, {
|
|
name: stalkerSourceName,
|
|
});
|
|
await openSources(app.mainWindow);
|
|
|
|
await expect
|
|
.poll(() => getVisibleSourceTitles(app.mainWindow))
|
|
.toHaveLength(4);
|
|
|
|
await selectSourceTypeFilter(app.mainWindow, 'M3U');
|
|
await expect
|
|
.poll(() => getVisibleSourceTitles(app.mainWindow))
|
|
.toEqual([urlSourceFileName, localSourceDisplayName]);
|
|
|
|
await selectSourceTypeFilter(app.mainWindow, 'Xtream');
|
|
await expect
|
|
.poll(() => getVisibleSourceTitles(app.mainWindow))
|
|
.toEqual([xtreamSourceName]);
|
|
|
|
await selectSourceTypeFilter(app.mainWindow, 'Stalker');
|
|
await expect
|
|
.poll(() => getVisibleSourceTitles(app.mainWindow))
|
|
.toEqual([stalkerSourceName]);
|
|
|
|
await selectSourceTypeFilter(app.mainWindow, 'All');
|
|
|
|
await selectSourceSort(app.mainWindow, 'Date added (Oldest first)');
|
|
await expect
|
|
.poll(() => getVisibleSourceTitles(app.mainWindow))
|
|
.toEqual([
|
|
localSourceDisplayName,
|
|
urlSourceFileName,
|
|
xtreamSourceName,
|
|
stalkerSourceName,
|
|
]);
|
|
|
|
await selectSourceSort(app.mainWindow, 'Name (A-Z)');
|
|
await expect
|
|
.poll(() => getVisibleSourceTitles(app.mainWindow))
|
|
.toEqual([
|
|
localSourceDisplayName,
|
|
stalkerSourceName,
|
|
xtreamSourceName,
|
|
urlSourceFileName,
|
|
]);
|
|
|
|
await selectSourceSort(app.mainWindow, 'Name (Z-A)');
|
|
await expect
|
|
.poll(() => getVisibleSourceTitles(app.mainWindow))
|
|
.toEqual([
|
|
urlSourceFileName,
|
|
xtreamSourceName,
|
|
stalkerSourceName,
|
|
localSourceDisplayName,
|
|
]);
|
|
|
|
await selectSourceSort(app.mainWindow, 'Date added (Newest first)');
|
|
await expect
|
|
.poll(() => getVisibleSourceTitles(app.mainWindow))
|
|
.toEqual([
|
|
stalkerSourceName,
|
|
xtreamSourceName,
|
|
urlSourceFileName,
|
|
localSourceDisplayName,
|
|
]);
|
|
|
|
await selectSourceSort(app.mainWindow, 'Custom order');
|
|
await dragSourceBefore(
|
|
app.mainWindow,
|
|
xtreamSourceName,
|
|
localSourceDisplayName
|
|
);
|
|
await expect
|
|
.poll(() => getVisibleSourceTitles(app.mainWindow))
|
|
.toEqual([
|
|
xtreamSourceName,
|
|
localSourceDisplayName,
|
|
urlSourceFileName,
|
|
stalkerSourceName,
|
|
]);
|
|
|
|
await app.mainWindow
|
|
.getByRole('link', { name: 'Dashboard', exact: true })
|
|
.click();
|
|
await openSources(app.mainWindow);
|
|
await expect
|
|
.poll(() => getVisibleSourceTitles(app.mainWindow))
|
|
.toEqual([
|
|
xtreamSourceName,
|
|
localSourceDisplayName,
|
|
urlSourceFileName,
|
|
stalkerSourceName,
|
|
]);
|
|
|
|
const restarted = await restartElectronApp(app, dataDir);
|
|
app.electronApp = restarted.electronApp;
|
|
app.mainWindow = restarted.mainWindow;
|
|
|
|
await openSources(app.mainWindow);
|
|
await expect
|
|
.poll(() => getVisibleSourceTitles(app.mainWindow))
|
|
.toEqual([
|
|
xtreamSourceName,
|
|
localSourceDisplayName,
|
|
urlSourceFileName,
|
|
stalkerSourceName,
|
|
]);
|
|
} finally {
|
|
await closeElectronApp(app);
|
|
await urlServer.close();
|
|
}
|
|
});
|
|
|
|
test('edits M3U, Xtream, and Stalker source details and keeps them after restart', async ({
|
|
dataDir,
|
|
request,
|
|
}) => {
|
|
await resetMockServers(request, ['xtream', 'stalker']);
|
|
const localFilePath = writeTemporaryM3uFile(
|
|
dataDir,
|
|
'editable-local-source.m3u',
|
|
[
|
|
{
|
|
groupTitle: 'Movies',
|
|
name: 'Editable Local Channel',
|
|
url: 'https://streams.example.test/editable-local.m3u8',
|
|
},
|
|
]
|
|
);
|
|
const app = await launchElectronApp(dataDir);
|
|
|
|
try {
|
|
await importM3uPlaylistFromNativeDialog(app, localFilePath);
|
|
await addXtreamPortal(app.mainWindow, {
|
|
name: 'Editable Xtream Source',
|
|
});
|
|
await addStalkerPortal(app.mainWindow, {
|
|
name: 'Editable Stalker Source',
|
|
});
|
|
await openSources(app.mainWindow);
|
|
|
|
let dialog = await openSourceEditor(
|
|
app.mainWindow,
|
|
editableLocalSourceDisplayName
|
|
);
|
|
await updateSourceDialog(dialog, {
|
|
autoRefresh: true,
|
|
title: 'Edited Local Source',
|
|
userAgent: 'Electron E2E Local/1.0',
|
|
});
|
|
await saveSourceDialog(app.mainWindow, dialog);
|
|
|
|
dialog = await openSourceEditor(
|
|
app.mainWindow,
|
|
'Editable Xtream Source'
|
|
);
|
|
await updateSourceDialog(dialog, {
|
|
password: 'pass1',
|
|
serverUrl: 'http://127.0.0.1:3211',
|
|
title: 'Edited Xtream Source',
|
|
username: 'user1',
|
|
});
|
|
await saveSourceDialog(app.mainWindow, dialog);
|
|
|
|
dialog = await openSourceEditor(
|
|
app.mainWindow,
|
|
'Editable Stalker Source'
|
|
);
|
|
await updateSourceDialog(dialog, {
|
|
macAddress: '00:1A:79:00:00:99',
|
|
portalUrl: 'http://127.0.0.1:3210/portal.php',
|
|
title: 'Edited Stalker Source',
|
|
});
|
|
await saveSourceDialog(app.mainWindow, dialog);
|
|
|
|
dialog = await openSourceEditor(
|
|
app.mainWindow,
|
|
'Edited Local Source'
|
|
);
|
|
await expectSourceDialogValues(dialog, {
|
|
autoRefresh: true,
|
|
title: 'Edited Local Source',
|
|
userAgent: 'Electron E2E Local/1.0',
|
|
});
|
|
await dialog
|
|
.getByRole('button', { name: 'Close', exact: true })
|
|
.click();
|
|
|
|
dialog = await openSourceEditor(
|
|
app.mainWindow,
|
|
'Edited Xtream Source'
|
|
);
|
|
await expectSourceDialogValues(dialog, {
|
|
password: 'pass1',
|
|
serverUrl: 'http://127.0.0.1:3211',
|
|
title: 'Edited Xtream Source',
|
|
username: 'user1',
|
|
});
|
|
await dialog
|
|
.getByRole('button', { name: 'Close', exact: true })
|
|
.click();
|
|
|
|
dialog = await openSourceEditor(
|
|
app.mainWindow,
|
|
'Edited Stalker Source'
|
|
);
|
|
await expectSourceDialogValues(dialog, {
|
|
macAddress: '00:1A:79:00:00:99',
|
|
portalUrl: 'http://127.0.0.1:3210/portal.php',
|
|
title: 'Edited Stalker Source',
|
|
});
|
|
await dialog
|
|
.getByRole('button', { name: 'Close', exact: true })
|
|
.click();
|
|
|
|
const restarted = await restartElectronApp(app, dataDir);
|
|
app.electronApp = restarted.electronApp;
|
|
app.mainWindow = restarted.mainWindow;
|
|
|
|
await openSources(app.mainWindow);
|
|
|
|
dialog = await openSourceEditor(
|
|
app.mainWindow,
|
|
'Edited Local Source'
|
|
);
|
|
await expectSourceDialogValues(dialog, {
|
|
autoRefresh: true,
|
|
title: 'Edited Local Source',
|
|
userAgent: 'Electron E2E Local/1.0',
|
|
});
|
|
await dialog
|
|
.getByRole('button', { name: 'Close', exact: true })
|
|
.click();
|
|
|
|
dialog = await openSourceEditor(
|
|
app.mainWindow,
|
|
'Edited Xtream Source'
|
|
);
|
|
await expectSourceDialogValues(dialog, {
|
|
password: 'pass1',
|
|
serverUrl: 'http://127.0.0.1:3211',
|
|
title: 'Edited Xtream Source',
|
|
username: 'user1',
|
|
});
|
|
await dialog
|
|
.getByRole('button', { name: 'Close', exact: true })
|
|
.click();
|
|
|
|
dialog = await openSourceEditor(
|
|
app.mainWindow,
|
|
'Edited Stalker Source'
|
|
);
|
|
await expectSourceDialogValues(dialog, {
|
|
macAddress: '00:1A:79:00:00:99',
|
|
portalUrl: 'http://127.0.0.1:3210/portal.php',
|
|
title: 'Edited Stalker Source',
|
|
});
|
|
} finally {
|
|
await closeElectronApp(app);
|
|
}
|
|
});
|
|
|
|
test('deletes M3U, Stalker, and Xtream sources from the sources view', async ({
|
|
dataDir,
|
|
request,
|
|
}) => {
|
|
await resetMockServers(request, ['xtream', 'stalker']);
|
|
const localFilePath = writeTemporaryM3uFile(
|
|
dataDir,
|
|
'deletable-local-source.m3u',
|
|
[
|
|
{
|
|
groupTitle: 'Live',
|
|
name: 'Delete Me Local',
|
|
url: 'https://streams.example.test/delete-local.m3u8',
|
|
},
|
|
]
|
|
);
|
|
const app = await launchElectronApp(dataDir);
|
|
|
|
try {
|
|
await importM3uPlaylistFromNativeDialog(app, localFilePath);
|
|
await addXtreamPortal(app.mainWindow, {
|
|
name: 'Delete Me Xtream',
|
|
});
|
|
await addStalkerPortal(app.mainWindow, {
|
|
name: 'Delete Me Stalker',
|
|
});
|
|
await openSources(app.mainWindow);
|
|
|
|
await deleteSource(app.mainWindow, deletableLocalSourceDisplayName);
|
|
await expect(
|
|
sourceRowByTitle(
|
|
app.mainWindow,
|
|
deletableLocalSourceDisplayName
|
|
)
|
|
).toHaveCount(0, {
|
|
timeout: 20000,
|
|
});
|
|
|
|
await deleteSource(app.mainWindow, 'Delete Me Stalker');
|
|
await expect(
|
|
sourceRowByTitle(app.mainWindow, 'Delete Me Stalker')
|
|
).toHaveCount(0, {
|
|
timeout: 20000,
|
|
});
|
|
|
|
await deleteSource(app.mainWindow, 'Delete Me Xtream');
|
|
await expect(
|
|
sourceRowByTitle(app.mainWindow, 'Delete Me Xtream')
|
|
).toHaveCount(0, {
|
|
timeout: 60000,
|
|
});
|
|
} finally {
|
|
await closeElectronApp(app);
|
|
}
|
|
});
|
|
|
|
test('refreshes local-file M3U, URL M3U, and Xtream sources from the sources view', async ({
|
|
dataDir,
|
|
request,
|
|
}) => {
|
|
await resetMockServers(request, ['xtream']);
|
|
const localFilePath = writeTemporaryM3uFile(
|
|
dataDir,
|
|
'refresh-local-source.m3u',
|
|
[
|
|
{
|
|
groupTitle: 'News',
|
|
name: 'Original Local Channel',
|
|
url: 'https://streams.example.test/original-local.m3u8',
|
|
},
|
|
]
|
|
);
|
|
const urlServer = await createMutableTextServer(
|
|
`#EXTM3U
|
|
#EXTINF:-1 group-title="News",Original URL Channel
|
|
https://streams.example.test/original-url.m3u8
|
|
`,
|
|
{
|
|
contentType: 'application/vnd.apple.mpegurl',
|
|
resourcePath: '/refresh-url-source.m3u',
|
|
}
|
|
);
|
|
const app = await launchElectronApp(dataDir);
|
|
|
|
try {
|
|
await importM3uPlaylistFromNativeDialog(app, localFilePath);
|
|
await importM3uPlaylistFromUrl(
|
|
app.mainWindow,
|
|
urlServer.resourceUrl
|
|
);
|
|
await addXtreamPortal(app.mainWindow, {
|
|
name: 'Refresh Xtream Source',
|
|
});
|
|
await openSources(app.mainWindow);
|
|
|
|
writeTemporaryM3uFile(dataDir, 'refresh-local-source.m3u', [
|
|
{
|
|
groupTitle: 'News',
|
|
name: 'Refreshed Local Channel',
|
|
url: 'https://streams.example.test/refreshed-local.m3u8',
|
|
},
|
|
]);
|
|
await refreshSource(app.mainWindow, refreshLocalSourceDisplayName);
|
|
await expectPlaylistUpdatedToast(app.mainWindow);
|
|
await sourceRowByTitle(
|
|
app.mainWindow,
|
|
refreshLocalSourceDisplayName
|
|
)
|
|
.first()
|
|
.click();
|
|
await waitForM3uCatalog(app.mainWindow);
|
|
await expect(
|
|
app.mainWindow.getByTestId('channel-item').filter({
|
|
hasText: 'Refreshed Local Channel',
|
|
})
|
|
).toHaveCount(1);
|
|
await openSources(app.mainWindow);
|
|
|
|
urlServer.setBody(
|
|
`#EXTM3U
|
|
#EXTINF:-1 group-title="News",Refreshed URL Channel
|
|
https://streams.example.test/refreshed-url.m3u8
|
|
`
|
|
);
|
|
await refreshSource(app.mainWindow, 'refresh-url-source.m3u');
|
|
await expectPlaylistUpdatedToast(app.mainWindow);
|
|
await waitForSourceRowIdle(
|
|
app.mainWindow,
|
|
'refresh-url-source.m3u'
|
|
);
|
|
await sourceRowByTitle(app.mainWindow, 'refresh-url-source.m3u')
|
|
.first()
|
|
.click();
|
|
await waitForM3uCatalog(app.mainWindow);
|
|
await expect(
|
|
app.mainWindow.getByTestId('channel-item').filter({
|
|
hasText: 'Refreshed URL Channel',
|
|
})
|
|
).toHaveCount(1);
|
|
await openSources(app.mainWindow);
|
|
|
|
await refreshSource(app.mainWindow, 'Refresh Xtream Source', {
|
|
confirm: true,
|
|
});
|
|
await waitForXtreamCatalog(app.mainWindow);
|
|
await waitForPortalDebugEvent(app.mainWindow, {
|
|
operation: 'get_live_categories',
|
|
provider: 'xtream',
|
|
timeoutMs: 30000,
|
|
});
|
|
await openSources(app.mainWindow);
|
|
await waitForSourceRowIdle(app.mainWindow, 'Refresh Xtream Source');
|
|
await expect(
|
|
sourceRowByTitle(app.mainWindow, 'Refresh Xtream Source')
|
|
.first()
|
|
.locator('.meta')
|
|
).toContainText('Updated:');
|
|
} finally {
|
|
await closeElectronApp(app);
|
|
await urlServer.close();
|
|
}
|
|
});
|
|
});
|