mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-15 05:36:17 -08:00
- Side list (UI-kit side list): title with the playlist count and an Add button, a Type group, and on desktop a Status group (Any / Responding / Not responding). The two filters combine and persist; each group counts within the other's choice. The chosen row uses one selected-row token (soft tint, outlined in light theme). - Page: a 28px title with the visible count instead of the bordered header bar; Clean up and the sort menu are secondary buttons. Clean up shows how many sources do not respond. Sort adds Last updated, Most channels and (desktop) Not responding first, and keeps Name Z-A and Custom order. - List: app-source-row with hover actions (Refresh, Edit, ⋯); a failing row keeps Retry in view. "No matches" offers Reset filters. Dragging in custom order no longer reuses positions of rows a filter hides. - Delete with Undo: no confirmation; the row leaves at once and the delete runs when the toast closes (its close button commits at once). Deleting the active source switches to the next row. A large Xtream delete shows its row as "Removing…" with progress and Cancel, as before. - The list's refresh flows moved into SourceListRefreshService; E2E and performance drivers follow the new markup and delete flow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
108 lines
3.5 KiB
TypeScript
108 lines
3.5 KiB
TypeScript
import { copyFileSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
import {
|
|
closeElectronApp,
|
|
expect,
|
|
importM3uPlaylistFromNativeDialog,
|
|
launchCompetingElectronInstance,
|
|
launchElectronApp,
|
|
m3uFixturePath,
|
|
openSources,
|
|
test,
|
|
} from './electron-test-fixtures';
|
|
|
|
test.describe('Electron Native Playlist Import', () => {
|
|
test('@m3u @electron imports an M3U playlist via the native file picker path', async ({
|
|
dataDir,
|
|
}) => {
|
|
const app = await launchElectronApp(dataDir);
|
|
|
|
try {
|
|
await importM3uPlaylistFromNativeDialog(app, m3uFixturePath);
|
|
|
|
await app.mainWindow.waitForURL(/\/workspace\/playlists\/.+/);
|
|
await expect(
|
|
app.mainWindow.getByTestId('channel-item')
|
|
).toHaveCount(4);
|
|
} finally {
|
|
await closeElectronApp(app);
|
|
}
|
|
});
|
|
|
|
test('@m3u @electron opens a playlist passed on the command line', async ({
|
|
dataDir,
|
|
}) => {
|
|
// Mirrors a file-association double-click on Windows/Linux: the path
|
|
// is the app's own argv, resolved and queued in the main process
|
|
// before the renderer exists.
|
|
const app = await launchElectronApp(dataDir, {
|
|
appArgs: [m3uFixturePath],
|
|
});
|
|
|
|
try {
|
|
await app.mainWindow.waitForURL(/\/workspace\/playlists\/.+/, {
|
|
timeout: 30000,
|
|
});
|
|
await expect(
|
|
app.mainWindow.getByTestId('channel-item')
|
|
).toHaveCount(4);
|
|
} finally {
|
|
await closeElectronApp(app);
|
|
}
|
|
});
|
|
|
|
test('@m3u @electron opens every playlist of a multi-file selection', async ({
|
|
dataDir,
|
|
}) => {
|
|
// Selecting several playlists in a file manager is a single launch
|
|
// with one argument per file, because the generated desktop entry
|
|
// ends in `%U`. Stopping at the first would drop the rest.
|
|
const secondPlaylist = join(dataDir, 'second-selection.m3u');
|
|
copyFileSync(m3uFixturePath, secondPlaylist);
|
|
|
|
const app = await launchElectronApp(dataDir, {
|
|
appArgs: [m3uFixturePath, secondPlaylist],
|
|
});
|
|
|
|
try {
|
|
await app.mainWindow.waitForURL(/\/workspace\/playlists\/.+/, {
|
|
timeout: 30000,
|
|
});
|
|
await openSources(app.mainWindow);
|
|
await expect(
|
|
app.mainWindow.locator('app-workspace-sources app-source-row')
|
|
).toHaveCount(2, { timeout: 30000 });
|
|
} finally {
|
|
await closeElectronApp(app);
|
|
}
|
|
});
|
|
|
|
test('@m3u @electron opens a playlist handed over by a second launch', async ({
|
|
dataDir,
|
|
}) => {
|
|
// The second process never gets a window: the single-instance guard
|
|
// makes it quit and forwards its argv to the running app.
|
|
const app = await launchElectronApp(dataDir);
|
|
|
|
try {
|
|
const competing = await launchCompetingElectronInstance(dataDir, {
|
|
appArgs: [m3uFixturePath],
|
|
});
|
|
|
|
expect(
|
|
competing.timedOut,
|
|
`Competing instance did not exit. stderr: ${competing.stderr}`
|
|
).toBe(false);
|
|
|
|
await app.mainWindow.waitForURL(/\/workspace\/playlists\/.+/, {
|
|
timeout: 30000,
|
|
});
|
|
await expect(
|
|
app.mainWindow.getByTestId('channel-item')
|
|
).toHaveCount(4);
|
|
} finally {
|
|
await closeElectronApp(app);
|
|
}
|
|
});
|
|
});
|