mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-16 22:26:16 -08:00
fix(epg): preserve UTF-8 characters across XMLTV stream chunks (#1946)
* fix(epg): preserve UTF-8 across XMLTV stream chunks * test(sources): wait for nested menu detachment before Escape
This commit is contained in:
1 parent
9a94570a6a
commit
34470ea273
6 files changed
+170
-15
No files matched your search
@@ -105,17 +105,25 @@ test.describe('Electron EPG', () => {
|
||||
['double', true],
|
||||
['single', false],
|
||||
] as const) {
|
||||
test(`@epg @electron imports ${name} gzip with a .gz URL and HTTP gzip`, async ({
|
||||
test(`@epg @electron imports ${name} gzip with UTF-8 across decompression chunks`, async ({
|
||||
dataDir,
|
||||
}) => {
|
||||
const server = await createGzipEpgServer(
|
||||
createCurrentXmltvFixture(
|
||||
'gzip-news',
|
||||
'Gzip News',
|
||||
'Gzip Bulletin'
|
||||
),
|
||||
doubleGzip
|
||||
const title = 'Été 日本 🎬 Новости';
|
||||
const xml = createCurrentXmltvFixture(
|
||||
'gzip-news',
|
||||
'Gzip Новости 東京 📺',
|
||||
title
|
||||
);
|
||||
// Gunzip emits 16 KiB chunks. Put the first byte of É at the end
|
||||
// of one chunk so the real parser worker must carry UTF-8 state.
|
||||
const titleOffset = Buffer.byteLength(
|
||||
xml.slice(0, xml.indexOf(title))
|
||||
);
|
||||
const padding = ' '.repeat(16 * 1024 - 1 - titleOffset - 7);
|
||||
const paddedXml = xml.replace('<tv>', `<tv><!--${padding}-->`);
|
||||
expect(Buffer.from(paddedXml)[16 * 1024 - 1]).toBe(0xc3);
|
||||
expect(Buffer.from(paddedXml)[16 * 1024]).toBe(0x89);
|
||||
const server = await createGzipEpgServer(paddedXml, doubleGzip);
|
||||
try {
|
||||
const app = await launchElectronApp(dataDir);
|
||||
try {
|
||||
@@ -129,13 +137,13 @@ test.describe('Electron EPG', () => {
|
||||
window.electron.getEpgChannelMetadata(['gzip-news'])
|
||||
);
|
||||
expect(metadata['gzip-news']).toMatchObject({
|
||||
displayName: 'Gzip News',
|
||||
displayName: 'Gzip Новости 東京 📺',
|
||||
});
|
||||
const programs = await app.mainWindow.evaluate(() =>
|
||||
window.electron.getChannelPrograms('gzip-news')
|
||||
);
|
||||
expect(programs.map((program) => program.title)).toEqual([
|
||||
'Gzip Bulletin',
|
||||
title,
|
||||
]);
|
||||
} finally {
|
||||
await closeElectronApp(app);
|
||||
|
||||
@@ -786,15 +786,24 @@ https://streams.example.test/original-url.m3u8
|
||||
await dropM3uPlaylistOntoWorkspace(app.mainWindow, localFilePath);
|
||||
await waitForM3uCatalog(app.mainWindow);
|
||||
// Refresh for the active source lives in its switcher row menu.
|
||||
await app.mainWindow.getByTestId('playlist-switcher-trigger').click();
|
||||
await app.mainWindow
|
||||
.getByTestId('playlist-switcher-trigger')
|
||||
.click();
|
||||
const currentRow = app.mainWindow.locator(
|
||||
'app-playlist-switcher-panel [role="option"][aria-current="true"]'
|
||||
);
|
||||
await currentRow.locator('.source-row__more').click();
|
||||
await expect(
|
||||
app.mainWindow.getByRole('menuitem', { name: 'Refresh playlist' })
|
||||
app.mainWindow.getByRole('menuitem', {
|
||||
name: 'Refresh playlist',
|
||||
})
|
||||
).toBeVisible();
|
||||
await app.mainWindow.keyboard.press('Escape');
|
||||
// Material retains the nested menu overlay during its exit animation.
|
||||
// Wait for detachment before sending Escape to the switcher beneath it.
|
||||
await expect(
|
||||
app.mainWindow.getByRole('menu', { includeHidden: true })
|
||||
).toHaveCount(0);
|
||||
await app.mainWindow.keyboard.press('Escape');
|
||||
await expect(
|
||||
app.mainWindow.locator('app-playlist-switcher-panel')
|
||||
|
||||
Reference in new issue
Block a user