Proyecto App Windows

This commit is contained in:
2026-05-03 23:36:48 +02:00
commit 96793b2e1e
1248 changed files with 189750 additions and 0 deletions
@@ -0,0 +1,505 @@
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();
}
});
});