UNPKG

hls-explore

Version:

Simple TUI for exploring HLS files

574 lines (492 loc) 29.1 kB
"use strict"; const fs = require("fs"); const nock = require("nock"); const { expect } = require("chai"); const hlsExplore = require("../index.js"); const playlistUrl = "http://example.com/playlist.m3u8"; const mockTerminal = { _lines: [], _sendKey: async () => {}, _exit: false, fullscreen() {}, clear() { this._lines.length = 0; }, hideCursor() {}, grabInput() {}, on(_, callback) { this._sendKey = callback; }, exit() {}, moveTo() {}, eraseLine() {}, processExit() { this._exit = true; }, bgGray: { black: storeText.bind(null, "gray/black ") }, bgGreen: { black: storeText.bind(null, "green/black") }, bgDefaultColor: { gray: storeText.bind(null, "gray "), green: storeText.bind(null, "green "), }, }; function storeText(color, text) { mockTerminal._lines.push({ color, text }); } const playlist = fs.readFileSync("./test/data/playlist.m3u8", "utf8"); const chunklist = fs.readFileSync("./test/data/chunklist", "utf8"); const subtitleList = fs.readFileSync("./test/data/subtitle_list", "utf8"); const webvtt = fs.readFileSync("./test/data/webvtt", "utf8"); const playlistPath = "/playlist.m3u8"; const chunklistPath = "/chunklist_b1500000_cfbmNvZGUvMjAyNS0wNS0yOS9ueHRlZGl0aW9uLVVtY1h5TGhWSnlIVHhnLTE3NDg1NTIxMzYvbnh0ZWRpdGlvbi1VbWNYeUxoVkp5SFR4Zy0xNzQ4NTUyMTM2LnZ0dA==.m3u8"; const mediaPath = "/media_b1500000_0.ts"; const subtitleListPath = "/subtitlelist_lswe_b1500000_cfbmNvZGUvMjAyNS0wNS0yOS9ueHRlZGl0aW9uLVVtY1h5TGhWSnlIVHhnLTE3NDg1NTIxMzYvbnh0ZWRpdGlvbi1VbWNYeUxoVkp5SFR4Zy0xNzQ4NTUyMTM2LnZ0dA==.m3u8"; const subtitleChunkPath = "/subtitlechunk_lswe_b1500000_cfbmNvZGUvMjAyNS0wNS0yOS9ueHRlZGl0aW9uLVVtY1h5TGhWSnlIVHhnLTE3NDg1NTIxMzYvbnh0ZWRpdGlvbi1VbWNYeUxoVkp5SFR4Zy0xNzQ4NTUyMTM2LnZ0dA==_0.webvtt"; const argv = [ "", "hls-explore", playlistUrl ]; Feature("HLS explore", () => { beforeEachScenario(() => { mockTerminal._lines.length = 0; mockTerminal._exit = false; }); afterEachFeature(() => { expect(nock.pendingMocks()).to.deep.equal([]); }); Scenario("Typical use case", () => { Given("the HLS files are available", () => { mockHeadAndGet(playlistPath, playlist, "application/vnd.apple.mpegurl"); mockHeadAndGet(chunklistPath, chunklist, "application/vnd.apple.mpegurl"); nock("http://example.com") .head(mediaPath) .reply(200, null, { "Content-Type": "video/MP2T" }); mockHeadAndGet(subtitleListPath, subtitleList, "application/vnd.apple.mpegurl"); mockHeadAndGet(subtitleChunkPath, webvtt, "text/plain"); }); When("running the program", async () => { await hlsExplore(mockTerminal, { argv }); }); Then("text should have been printed", () => { expect(mockTerminal._lines).to.deep.equal([ { color: "gray/black ", text: "#EXTM3U" }, { color: "gray ", text: "#EXT-X-VERSION:3" }, { color: "green ", text: "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"Svenska\",FORCED=NO,AUTOSELECT=YES,URI=\"subtitlelist_lswe_b1500000_cfbmNvZGUvMjAyNS0wNS0yOS9ueHRlZGl0aW9uLVVtY1h5TGhWSnlIVHhnLTE3NDg1NTIxMzYvbnh0ZWRpdGlvbi1VbWNYeUxoVkp5SFR4Zy0xNzQ4NTUyMTM2LnZ0dA==.m3u8\",LANGUAGE=\"swe\"" }, { color: "gray ", text: "#EXT-X-STREAM-INF:BANDWIDTH=1500000,SUBTITLES=\"subs\"" }, { color: "green ", text: "chunklist_b1500000_cfbmNvZGUvMjAyNS0wNS0yOS9ueHRlZGl0aW9uLVVtY1h5TGhWSnlIVHhnLTE3NDg1NTIxMzYvbnh0ZWRpdGlvbi1VbWNYeUxoVkp5SFR4Zy0xNzQ4NTUyMTM2LnZ0dA==.m3u8" }, { color: "green ", text: "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"Svenska\",FORCED=NO,AUTOSELECT=YES,URI=\"subtitlelist_lswe_b5000000_cfbmNvZGUvMjAyNS0wNS0yOS9ueHRlZGl0aW9uLVVtY1h5TGhWSnlIVHhnLTE3NDg1NTIxMzYvbnh0ZWRpdGlvbi1VbWNYeUxoVkp5SFR4Zy0xNzQ4NTUyMTM2LnZ0dA==.m3u8\",LANGUAGE=\"swe\"" }, { color: "gray ", text: "#EXT-X-STREAM-INF:BANDWIDTH=5000000,SUBTITLES=\"subs\"" }, { color: "green ", text: "chunklist_b5000000_cfbmNvZGUvMjAyNS0wNS0yOS9ueHRlZGl0aW9uLVVtY1h5TGhWSnlIVHhnLTE3NDg1NTIxMzYvbnh0ZWRpdGlvbi1VbWNYeUxoVkp5SFR4Zy0xNzQ4NTUyMTM2LnZ0dA==.m3u8" }, { color: "green ", text: "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"Svenska\",FORCED=NO,AUTOSELECT=YES,URI=\"subtitlelist_lswe_b3500000_cfbmNvZGUvMjAyNS0wNS0yOS9ueHRlZGl0aW9uLVVtY1h5TGhWSnlIVHhnLTE3NDg1NTIxMzYvbnh0ZWRpdGlvbi1VbWNYeUxoVkp5SFR4Zy0xNzQ4NTUyMTM2LnZ0dA==.m3u8\",LANGUAGE=\"swe\"" }, { color: "gray ", text: "#EXT-X-STREAM-INF:BANDWIDTH=3500000,SUBTITLES=\"subs\"" }, { color: "green ", text: "chunklist_b3500000_cfbmNvZGUvMjAyNS0wNS0yOS9ueHRlZGl0aW9uLVVtY1h5TGhWSnlIVHhnLTE3NDg1NTIxMzYvbnh0ZWRpdGlvbi1VbWNYeUxoVkp5SFR4Zy0xNzQ4NTUyMTM2LnZ0dA==.m3u8" }, { color: "green ", text: "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"Svenska\",FORCED=NO,AUTOSELECT=YES,URI=\"subtitlelist_lswe_b1000000_cfbmNvZGUvMjAyNS0wNS0yOS9ueHRlZGl0aW9uLVVtY1h5TGhWSnlIVHhnLTE3NDg1NTIxMzYvbnh0ZWRpdGlvbi1VbWNYeUxoVkp5SFR4Zy0xNzQ4NTUyMTM2LnZ0dA==.m3u8\",LANGUAGE=\"swe\"" }, { color: "gray ", text: "#EXT-X-STREAM-INF:BANDWIDTH=1000000,SUBTITLES=\"subs\"" }, { color: "green ", text: "chunklist_b1000000_cfbmNvZGUvMjAyNS0wNS0yOS9ueHRlZGl0aW9uLVVtY1h5TGhWSnlIVHhnLTE3NDg1NTIxMzYvbnh0ZWRpdGlvbi1VbWNYeUxoVkp5SFR4Zy0xNzQ4NTUyMTM2LnZ0dA==.m3u8" }, { color: "green ", text: "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"Svenska\",FORCED=NO,AUTOSELECT=YES,URI=\"subtitlelist_lswe_b250000_cfbmNvZGUvMjAyNS0wNS0yOS9ueHRlZGl0aW9uLVVtY1h5TGhWSnlIVHhnLTE3NDg1NTIxMzYvbnh0ZWRpdGlvbi1VbWNYeUxoVkp5SFR4Zy0xNzQ4NTUyMTM2LnZ0dA==.m3u8\",LANGUAGE=\"swe\"" }, { color: "gray ", text: "#EXT-X-STREAM-INF:BANDWIDTH=250000,SUBTITLES=\"subs\"" }, { color: "green ", text: "chunklist_b250000_cfbmNvZGUvMjAyNS0wNS0yOS9ueHRlZGl0aW9uLVVtY1h5TGhWSnlIVHhnLTE3NDg1NTIxMzYvbnh0ZWRpdGlvbi1VbWNYeUxoVkp5SFR4Zy0xNzQ4NTUyMTM2LnZ0dA==.m3u8" }, ]); }); When("trying to expand a non-expandable row", () => { return mockTerminal._sendKey("ENTER"); }); Then("nothing should have happened", () => { expect(mockTerminal._lines).to.deep.equal([ { color: "gray/black ", text: "#EXTM3U" }, { color: "gray ", text: "#EXT-X-VERSION:3" }, { color: "green ", text: "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"Svenska\",FORCED=NO,AUTOSELECT=YES,URI=\"subtitlelist_lswe_b1500000_cfbmNvZGUvMjAyNS0wNS0yOS9ueHRlZGl0aW9uLVVtY1h5TGhWSnlIVHhnLTE3NDg1NTIxMzYvbnh0ZWRpdGlvbi1VbWNYeUxoVkp5SFR4Zy0xNzQ4NTUyMTM2LnZ0dA==.m3u8\",LANGUAGE=\"swe\"" }, { color: "gray ", text: "#EXT-X-STREAM-INF:BANDWIDTH=1500000,SUBTITLES=\"subs\"" }, { color: "green ", text: "chunklist_b1500000_cfbmNvZGUvMjAyNS0wNS0yOS9ueHRlZGl0aW9uLVVtY1h5TGhWSnlIVHhnLTE3NDg1NTIxMzYvbnh0ZWRpdGlvbi1VbWNYeUxoVkp5SFR4Zy0xNzQ4NTUyMTM2LnZ0dA==.m3u8" }, { color: "green ", text: "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"Svenska\",FORCED=NO,AUTOSELECT=YES,URI=\"subtitlelist_lswe_b5000000_cfbmNvZGUvMjAyNS0wNS0yOS9ueHRlZGl0aW9uLVVtY1h5TGhWSnlIVHhnLTE3NDg1NTIxMzYvbnh0ZWRpdGlvbi1VbWNYeUxoVkp5SFR4Zy0xNzQ4NTUyMTM2LnZ0dA==.m3u8\",LANGUAGE=\"swe\"" }, { color: "gray ", text: "#EXT-X-STREAM-INF:BANDWIDTH=5000000,SUBTITLES=\"subs\"" }, { color: "green ", text: "chunklist_b5000000_cfbmNvZGUvMjAyNS0wNS0yOS9ueHRlZGl0aW9uLVVtY1h5TGhWSnlIVHhnLTE3NDg1NTIxMzYvbnh0ZWRpdGlvbi1VbWNYeUxoVkp5SFR4Zy0xNzQ4NTUyMTM2LnZ0dA==.m3u8" }, { color: "green ", text: "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"Svenska\",FORCED=NO,AUTOSELECT=YES,URI=\"subtitlelist_lswe_b3500000_cfbmNvZGUvMjAyNS0wNS0yOS9ueHRlZGl0aW9uLVVtY1h5TGhWSnlIVHhnLTE3NDg1NTIxMzYvbnh0ZWRpdGlvbi1VbWNYeUxoVkp5SFR4Zy0xNzQ4NTUyMTM2LnZ0dA==.m3u8\",LANGUAGE=\"swe\"" }, { color: "gray ", text: "#EXT-X-STREAM-INF:BANDWIDTH=3500000,SUBTITLES=\"subs\"" }, { color: "green ", text: "chunklist_b3500000_cfbmNvZGUvMjAyNS0wNS0yOS9ueHRlZGl0aW9uLVVtY1h5TGhWSnlIVHhnLTE3NDg1NTIxMzYvbnh0ZWRpdGlvbi1VbWNYeUxoVkp5SFR4Zy0xNzQ4NTUyMTM2LnZ0dA==.m3u8" }, { color: "green ", text: "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"Svenska\",FORCED=NO,AUTOSELECT=YES,URI=\"subtitlelist_lswe_b1000000_cfbmNvZGUvMjAyNS0wNS0yOS9ueHRlZGl0aW9uLVVtY1h5TGhWSnlIVHhnLTE3NDg1NTIxMzYvbnh0ZWRpdGlvbi1VbWNYeUxoVkp5SFR4Zy0xNzQ4NTUyMTM2LnZ0dA==.m3u8\",LANGUAGE=\"swe\"" }, { color: "gray ", text: "#EXT-X-STREAM-INF:BANDWIDTH=1000000,SUBTITLES=\"subs\"" }, { color: "green ", text: "chunklist_b1000000_cfbmNvZGUvMjAyNS0wNS0yOS9ueHRlZGl0aW9uLVVtY1h5TGhWSnlIVHhnLTE3NDg1NTIxMzYvbnh0ZWRpdGlvbi1VbWNYeUxoVkp5SFR4Zy0xNzQ4NTUyMTM2LnZ0dA==.m3u8" }, { color: "green ", text: "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"Svenska\",FORCED=NO,AUTOSELECT=YES,URI=\"subtitlelist_lswe_b250000_cfbmNvZGUvMjAyNS0wNS0yOS9ueHRlZGl0aW9uLVVtY1h5TGhWSnlIVHhnLTE3NDg1NTIxMzYvbnh0ZWRpdGlvbi1VbWNYeUxoVkp5SFR4Zy0xNzQ4NTUyMTM2LnZ0dA==.m3u8\",LANGUAGE=\"swe\"" }, { color: "gray ", text: "#EXT-X-STREAM-INF:BANDWIDTH=250000,SUBTITLES=\"subs\"" }, { color: "green ", text: "chunklist_b250000_cfbmNvZGUvMjAyNS0wNS0yOS9ueHRlZGl0aW9uLVVtY1h5TGhWSnlIVHhnLTE3NDg1NTIxMzYvbnh0ZWRpdGlvbi1VbWNYeUxoVkp5SFR4Zy0xNzQ4NTUyMTM2LnZ0dA==.m3u8" }, ]); }); When("moving down two rows", async () => { await mockTerminal._sendKey("j"); await mockTerminal._sendKey("DOWN"); }); Then("the third row should be highlighted", () => { expect(mockTerminal._lines[2]).to.deep.equal({ color: "green/black", text: "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"Svenska\",FORCED=NO,AUTOSELECT=YES,URI=\"subtitlelist_lswe_b1500000_cfbmNvZGUvMjAyNS0wNS0yOS9ueHRlZGl0aW9uLVVtY1h5TGhWSnlIVHhnLTE3NDg1NTIxMzYvbnh0ZWRpdGlvbi1VbWNYeUxoVkp5SFR4Zy0xNzQ4NTUyMTM2LnZ0dA==.m3u8\",LANGUAGE=\"swe\"", }); }); When("moving to the next expandable row", async () => { await mockTerminal._sendKey("TAB"); }); Then("the fifth row should be highlighted", () => { expect(mockTerminal._lines.filter((line) => line.color === "green/black")).to.have.length(1); expect(mockTerminal._lines[4]).to.deep.equal({ color: "green/black", text: "chunklist_b1500000_cfbmNvZGUvMjAyNS0wNS0yOS9ueHRlZGl0aW9uLVVtY1h5TGhWSnlIVHhnLTE3NDg1NTIxMzYvbnh0ZWRpdGlvbi1VbWNYeUxoVkp5SFR4Zy0xNzQ4NTUyMTM2LnZ0dA==.m3u8", }); }); When("moving up two rows", async () => { await mockTerminal._sendKey("k"); await mockTerminal._sendKey("UP"); }); Then("the third row should be highlighted", () => { expect(mockTerminal._lines[2]).to.deep.equal({ color: "green/black", text: "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"Svenska\",FORCED=NO,AUTOSELECT=YES,URI=\"subtitlelist_lswe_b1500000_cfbmNvZGUvMjAyNS0wNS0yOS9ueHRlZGl0aW9uLVVtY1h5TGhWSnlIVHhnLTE3NDg1NTIxMzYvbnh0ZWRpdGlvbi1VbWNYeUxoVkp5SFR4Zy0xNzQ4NTUyMTM2LnZ0dA==.m3u8\",LANGUAGE=\"swe\"", }); }); When("moving back to the next expandable row", async () => { await mockTerminal._sendKey("TAB"); }); And("expanding the chunklist", async () => { await mockTerminal._sendKey("ENTER"); }); Then("the screen should show the chunklist", () => { expect(mockTerminal._lines).to.deep.equal([ { color: "gray/black ", text: "#EXTM3U" }, { color: "gray ", text: "#EXT-X-VERSION:3" }, { color: "gray ", text: "#EXT-X-TARGETDURATION:10" }, { color: "gray ", text: "#EXT-X-MEDIA-SEQUENCE:0" }, { color: "gray ", text: "#EXTINF:10.0," }, { color: "green ", text: "media_b1500000_0.ts" }, { color: "gray ", text: "#EXTINF:10.0," }, { color: "green ", text: "media_b1500000_1.ts" }, { color: "gray ", text: "#EXTINF:10.0," }, { color: "green ", text: "media_b1500000_2.ts" }, { color: "gray ", text: "#EXTINF:10.0," }, { color: "green ", text: "media_b1500000_3.ts" }, { color: "gray ", text: "#EXTINF:10.0," }, { color: "green ", text: "media_b1500000_4.ts" }, { color: "gray ", text: "#EXTINF:4.144," }, { color: "green ", text: "media_b1500000_5.ts" }, { color: "gray ", text: "#EXT-X-ENDLIST" }, ]); }); When("moving to the second expandable row", async () => { await mockTerminal._sendKey("TAB"); await mockTerminal._sendKey("TAB"); }); Then("the correct row should be selected", () => { expect(mockTerminal._lines.filter((line) => line.color === "green/black")).to.have.length(1); expect(mockTerminal._lines[7]).to.deep.equal({ color: "green/black", text: "media_b1500000_1.ts", }); }); When("moving to the previous expandable row", async () => { await mockTerminal._sendKey("SHIFT_TAB"); }); Then("the first expandable row should be selected", () => { expect(mockTerminal._lines.filter((line) => line.color === "green/black")).to.have.length(1); expect(mockTerminal._lines[5]).to.deep.equal({ color: "green/black", text: "media_b1500000_0.ts", }); }); When("trying to expand the chunk", async () => { await mockTerminal._sendKey("ENTER"); }); Then("an error message should be present", () => { expect(mockTerminal._lines).to.deep.equal([ { color: "gray/black ", text: "Unsupported Content-Type: video/MP2T" }, ]); }); When("going back one step", async () => { await mockTerminal._sendKey("q"); }); Then("the previous view should be rendered", () => { expect(mockTerminal._lines).to.deep.equal([ { color: "gray ", text: "#EXTM3U" }, { color: "gray ", text: "#EXT-X-VERSION:3" }, { color: "gray ", text: "#EXT-X-TARGETDURATION:10" }, { color: "gray ", text: "#EXT-X-MEDIA-SEQUENCE:0" }, { color: "gray ", text: "#EXTINF:10.0," }, { color: "green/black", text: "media_b1500000_0.ts" }, { color: "gray ", text: "#EXTINF:10.0," }, { color: "green ", text: "media_b1500000_1.ts" }, { color: "gray ", text: "#EXTINF:10.0," }, { color: "green ", text: "media_b1500000_2.ts" }, { color: "gray ", text: "#EXTINF:10.0," }, { color: "green ", text: "media_b1500000_3.ts" }, { color: "gray ", text: "#EXTINF:10.0," }, { color: "green ", text: "media_b1500000_4.ts" }, { color: "gray ", text: "#EXTINF:4.144," }, { color: "green ", text: "media_b1500000_5.ts" }, { color: "gray ", text: "#EXT-X-ENDLIST" }, ]); }); When("backing up one more step", async () => { await mockTerminal._sendKey("q"); }); Then("the first view should be loaded again", () => { expect(mockTerminal._lines).to.deep.equal([ { color: "gray ", text: "#EXTM3U" }, { color: "gray ", text: "#EXT-X-VERSION:3" }, { color: "green ", text: "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"Svenska\",FORCED=NO,AUTOSELECT=YES,URI=\"subtitlelist_lswe_b1500000_cfbmNvZGUvMjAyNS0wNS0yOS9ueHRlZGl0aW9uLVVtY1h5TGhWSnlIVHhnLTE3NDg1NTIxMzYvbnh0ZWRpdGlvbi1VbWNYeUxoVkp5SFR4Zy0xNzQ4NTUyMTM2LnZ0dA==.m3u8\",LANGUAGE=\"swe\"" }, { color: "gray ", text: "#EXT-X-STREAM-INF:BANDWIDTH=1500000,SUBTITLES=\"subs\"" }, { color: "green/black", text: "chunklist_b1500000_cfbmNvZGUvMjAyNS0wNS0yOS9ueHRlZGl0aW9uLVVtY1h5TGhWSnlIVHhnLTE3NDg1NTIxMzYvbnh0ZWRpdGlvbi1VbWNYeUxoVkp5SFR4Zy0xNzQ4NTUyMTM2LnZ0dA==.m3u8" }, { color: "green ", text: "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"Svenska\",FORCED=NO,AUTOSELECT=YES,URI=\"subtitlelist_lswe_b5000000_cfbmNvZGUvMjAyNS0wNS0yOS9ueHRlZGl0aW9uLVVtY1h5TGhWSnlIVHhnLTE3NDg1NTIxMzYvbnh0ZWRpdGlvbi1VbWNYeUxoVkp5SFR4Zy0xNzQ4NTUyMTM2LnZ0dA==.m3u8\",LANGUAGE=\"swe\"" }, { color: "gray ", text: "#EXT-X-STREAM-INF:BANDWIDTH=5000000,SUBTITLES=\"subs\"" }, { color: "green ", text: "chunklist_b5000000_cfbmNvZGUvMjAyNS0wNS0yOS9ueHRlZGl0aW9uLVVtY1h5TGhWSnlIVHhnLTE3NDg1NTIxMzYvbnh0ZWRpdGlvbi1VbWNYeUxoVkp5SFR4Zy0xNzQ4NTUyMTM2LnZ0dA==.m3u8" }, { color: "green ", text: "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"Svenska\",FORCED=NO,AUTOSELECT=YES,URI=\"subtitlelist_lswe_b3500000_cfbmNvZGUvMjAyNS0wNS0yOS9ueHRlZGl0aW9uLVVtY1h5TGhWSnlIVHhnLTE3NDg1NTIxMzYvbnh0ZWRpdGlvbi1VbWNYeUxoVkp5SFR4Zy0xNzQ4NTUyMTM2LnZ0dA==.m3u8\",LANGUAGE=\"swe\"" }, { color: "gray ", text: "#EXT-X-STREAM-INF:BANDWIDTH=3500000,SUBTITLES=\"subs\"" }, { color: "green ", text: "chunklist_b3500000_cfbmNvZGUvMjAyNS0wNS0yOS9ueHRlZGl0aW9uLVVtY1h5TGhWSnlIVHhnLTE3NDg1NTIxMzYvbnh0ZWRpdGlvbi1VbWNYeUxoVkp5SFR4Zy0xNzQ4NTUyMTM2LnZ0dA==.m3u8" }, { color: "green ", text: "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"Svenska\",FORCED=NO,AUTOSELECT=YES,URI=\"subtitlelist_lswe_b1000000_cfbmNvZGUvMjAyNS0wNS0yOS9ueHRlZGl0aW9uLVVtY1h5TGhWSnlIVHhnLTE3NDg1NTIxMzYvbnh0ZWRpdGlvbi1VbWNYeUxoVkp5SFR4Zy0xNzQ4NTUyMTM2LnZ0dA==.m3u8\",LANGUAGE=\"swe\"" }, { color: "gray ", text: "#EXT-X-STREAM-INF:BANDWIDTH=1000000,SUBTITLES=\"subs\"" }, { color: "green ", text: "chunklist_b1000000_cfbmNvZGUvMjAyNS0wNS0yOS9ueHRlZGl0aW9uLVVtY1h5TGhWSnlIVHhnLTE3NDg1NTIxMzYvbnh0ZWRpdGlvbi1VbWNYeUxoVkp5SFR4Zy0xNzQ4NTUyMTM2LnZ0dA==.m3u8" }, { color: "green ", text: "#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"Svenska\",FORCED=NO,AUTOSELECT=YES,URI=\"subtitlelist_lswe_b250000_cfbmNvZGUvMjAyNS0wNS0yOS9ueHRlZGl0aW9uLVVtY1h5TGhWSnlIVHhnLTE3NDg1NTIxMzYvbnh0ZWRpdGlvbi1VbWNYeUxoVkp5SFR4Zy0xNzQ4NTUyMTM2LnZ0dA==.m3u8\",LANGUAGE=\"swe\"" }, { color: "gray ", text: "#EXT-X-STREAM-INF:BANDWIDTH=250000,SUBTITLES=\"subs\"" }, { color: "green ", text: "chunklist_b250000_cfbmNvZGUvMjAyNS0wNS0yOS9ueHRlZGl0aW9uLVVtY1h5TGhWSnlIVHhnLTE3NDg1NTIxMzYvbnh0ZWRpdGlvbi1VbWNYeUxoVkp5SFR4Zy0xNzQ4NTUyMTM2LnZ0dA==.m3u8" }, ]); }); When("moving to the first subtitle row, and expanding it", async () => { await mockTerminal._sendKey("SHIFT_TAB"); await mockTerminal._sendKey("ENTER"); }); Then("The subtitle list should be displayed", () => { expect(mockTerminal._lines).to.deep.equal([ { color: "gray/black ", text: "#EXTM3U" }, { color: "gray ", text: "#EXT-X-VERSION:3" }, { color: "gray ", text: "#EXT-X-TARGETDURATION:250" }, { color: "gray ", text: "#EXT-X-MEDIA-SEQUENCE:0" }, { color: "gray ", text: "#EXTINF:54.144," }, { color: "green ", text: "subtitlechunk_lswe_b1500000_cfbmNvZGUvMjAyNS0wNS0yOS9ueHRlZGl0aW9uLVVtY1h5TGhWSnlIVHhnLTE3NDg1NTIxMzYvbnh0ZWRpdGlvbi1VbWNYeUxoVkp5SFR4Zy0xNzQ4NTUyMTM2LnZ0dA==_0.webvtt" }, { color: "gray ", text: "#EXT-X-ENDLIST" }, ]); }); When("moving to the subtitle chunk, and expanding it", async () => { await mockTerminal._sendKey("TAB"); await mockTerminal._sendKey("ENTER"); }); Then("The subtitles should be displayed", () => { expect(mockTerminal._lines).to.deep.equal([ { color: "green/black", text: "WEBVTT" }, { color: "green ", text: "X-TIMESTAMP-MAP=MPEGTS:0,LOCAL:00:00:00.000" }, { color: "gray ", text: "00:00:00.000 --> 00:00:03.870" }, { color: "green ", text: "Folk är verkligen panikslagna eftersom" }, { color: "green ", text: "de inte vet om de kommer kunna" }, { color: "gray ", text: "00:00:04.010 --> 00:00:06.750" }, { color: "green ", text: "komma tillbaka nästa termin, eller om" }, { color: "green ", text: "campus kommer att se likadant ut." }, { color: "gray ", text: "00:00:07.090 --> 00:00:11.510" }, { color: "green ", text: "Tvisten mellan Harvard och Trump" }, { color: "green ", text: "fortsätter efter att skolan vägrade krav" }, { color: "gray ", text: "00:00:11.510 --> 00:00:13.720" }, { color: "green ", text: "på bland annat mindre mångfaldsarbete." }, { color: "gray ", text: "00:00:14.430 --> 00:00:18.820" }, { color: "green ", text: "Den senaste smällen kom när Trump förbjöd" }, { color: "green ", text: "skolan att ta emot internationella" }, { color: "gray ", text: "00:00:18.820 --> 00:00:23.230" }, { color: "green ", text: "studenter, ett beslut som tillfälligt" }, { color: "green ", text: "stoppats av en federal domare." }, { color: "gray ", text: "00:00:23.640 --> 00:00:27.690" }, { color: "green ", text: "Svenske Leo Gerdén har studerat" }, { color: "green ", text: "vid universitetet i fyra år." }, { color: "gray ", text: "00:00:27.690 --> 00:00:30.510" }, { color: "green ", text: "Utan internationella studenter" }, { color: "green ", text: "är Harvard inte Harvard." }, { color: "gray ", text: "00:00:31.320 --> 00:00:36.570" }, { color: "green ", text: "Harvard lever på att kunna samla de" }, { color: "green ", text: "bästa och smartaste från hela världen," }, { color: "gray ", text: "00:00:36.710 --> 00:00:39.200" }, { color: "green ", text: "sätta oss i samma matsalar" }, { color: "green ", text: "och i samma klassrum." }, { color: "gray ", text: "00:00:39.650 --> 00:00:43.340" }, { color: "green ", text: "På torsdagen var det" }, { color: "green ", text: "examensceremoni, där oron för" }, { color: "gray ", text: "00:00:43.340 --> 00:00:45.280" }, { color: "green ", text: "Trumps hot har präglat stämningen." }, { color: "gray ", text: "00:00:45.670 --> 00:00:48.850" }, { color: "green ", text: "Jag tycker att det hänger" }, { color: "green ", text: "över hela examensveckan" }, { color: "gray ", text: "00:00:49.140 --> 00:00:53.570" }, { color: "green ", text: "eftersom vi lämnar en plats som" }, { color: "green ", text: "kanske inte är densamma nästa termin." }, ]); }); When("closing the app", async () => { await mockTerminal._sendKey("Q"); }); Then("the process should have ended", () => { expect(mockTerminal._exit).to.equal(true); }); }); Scenario("Backing from the first view exists", () => { Given("the HLS files are available", () => { mockHeadAndGet("/playlist.m3u8", playlist, "application/vnd.apple.mpegurl"); }); When("running the program", async () => { await hlsExplore(mockTerminal, { argv }); }); Then("the screen buffer should be filled", () => { expect(mockTerminal._lines).to.have.length(17); }); When("pressing back", async () => { await mockTerminal._sendKey("q"); }); Then("the process should have ended", () => { expect(mockTerminal._exit).to.equal(true); }); }); Scenario("Caching", () => { Given("the HLS files are available", () => { mockHeadAndGet("/playlist.m3u8", playlist, "application/vnd.apple.mpegurl"); mockHeadAndGet("/subtitlelist_lswe_b1500000_cfbmNvZGUvMjAyNS0wNS0yOS9ueHRlZGl0aW9uLVVtY1h5TGhWSnlIVHhnLTE3NDg1NTIxMzYvbnh0ZWRpdGlvbi1VbWNYeUxoVkp5SFR4Zy0xNzQ4NTUyMTM2LnZ0dA==.m3u8", subtitleList, "application/vnd.apple.mpegurl"); }); When("running the program", async () => { await hlsExplore(mockTerminal, { argv }); }); Then("the screen buffer should be filled", () => { expect(mockTerminal._lines).to.have.length(17); }); When("moving to the first expandable row, and expanding it", async () => { await mockTerminal._sendKey("TAB"); await mockTerminal._sendKey("ENTER"); }); Then("the chunklist should be displayed", () => { expect(mockTerminal._lines).to.have.length(7); }); And("no pending network mocks should exist", () => { expect(nock.pendingMocks()).to.deep.equal([]); }); When("going back", async () => { await mockTerminal._sendKey("q"); }); Then("the old view should be visible", () => { expect(mockTerminal._lines).to.have.length(17); }); And("expanding the row again", async () => { await mockTerminal._sendKey("ENTER"); }); Then("the chunklist should be displayed again, from cache", () => { expect(mockTerminal._lines).to.have.length(7); }); }); Scenario("Missing URL", () => { let mockProcess; Given("a mock process", () => { mockProcess = { exitCode: null, stderr: { write: function (data) { this._data = data; }, }, argv: argv.slice(0, -1), }; }); When("running the program", async () => { await hlsExplore(mockTerminal, mockProcess); }); Then("the program should not have stared", () => { expect(mockTerminal._lines).to.deep.equal([]); expect(mockTerminal._exit).to.equal(false); }); And("an error message should be displayed", () => { expect(mockProcess.stderr._data).to.equal("Usage: hls-explore <url>\n"); }); And("the exit code should be set to 1", () => { expect(mockProcess.exitCode).to.equal(1); }); }); Scenario("Invalid URL", () => { let mockProcess; Given("a mock process", () => { mockProcess = { exitCode: null, stderr: { write: function (data) { this._data = data; }, }, argv: [ "", "hls-explore", "invalid-url" ], }; }); When("running the program", async () => { await hlsExplore(mockTerminal, mockProcess); }); Then("the program should not have stared", () => { expect(mockTerminal._lines).to.deep.equal([]); expect(mockTerminal._exit).to.equal(false); }); And("an error message should be displayed", () => { expect(mockProcess.stderr._data).to.equal("The provided URL is not valid.\n"); }); And("the exit code should be set to 2", () => { expect(mockProcess.exitCode).to.equal(2); }); }); Scenario("404 from the HLS data", () => { let mockProcess; Given("a mock process", () => { mockProcess = { stdout: { write: function (data) { this._data = data; }, }, argv, }; }); And("the HLS file is available", () => { mockHeadAndGet("/playlist.m3u8", playlist, "application/vnd.apple.mpegurl"); }); When("running the program", async () => { await hlsExplore(mockTerminal, mockProcess); }); And("jumping to the first expandable row", async () => { await mockTerminal._sendKey("TAB"); }); And("pressing the print key", async () => { await mockTerminal._sendKey("p"); }); Then("the process should have ended", () => { expect(mockTerminal._exit).to.equal(true); }); And("the URL should be printed to stdout", () => { expect(mockProcess.stdout._data).to.equal("http://example.com/subtitlelist_lswe_b1500000_cfbmNvZGUvMjAyNS0wNS0yOS9ueHRlZGl0aW9uLVVtY1h5TGhWSnlIVHhnLTE3NDg1NTIxMzYvbnh0ZWRpdGlvbi1VbWNYeUxoVkp5SFR4Zy0xNzQ4NTUyMTM2LnZ0dA==.m3u8\n"); }); }); Scenario("Printing URL to resource", () => { let mockProcess; Given("a mock process", () => { mockProcess = { stdout: { write: function (data) { this._data = data; }, }, argv, }; }); And("the HLS file is available", () => { mockHeadAndGet("/playlist.m3u8", playlist, "application/vnd.apple.mpegurl"); }); When("running the program", async () => { await hlsExplore(mockTerminal, mockProcess); }); And("jumping to the first expandable row", async () => { await mockTerminal._sendKey("TAB"); }); And("pressing the print key", async () => { await mockTerminal._sendKey("p"); }); Then("the process should have ended", () => { expect(mockTerminal._exit).to.equal(true); }); And("the URL should be printed to stdout", () => { expect(mockProcess.stdout._data).to.equal("http://example.com/subtitlelist_lswe_b1500000_cfbmNvZGUvMjAyNS0wNS0yOS9ueHRlZGl0aW9uLVVtY1h5TGhWSnlIVHhnLTE3NDg1NTIxMzYvbnh0ZWRpdGlvbi1VbWNYeUxoVkp5SFR4Zy0xNzQ4NTUyMTM2LnZ0dA==.m3u8\n"); }); }); }); function mockHeadAndGet(path, responseBody, contentType) { nock("http://example.com") .head(path) .reply(200, null, { "Content-Type": contentType }) .get(path) .reply(200, responseBody, { "Content-Type": contentType }); }