Fix typechecking

This commit is contained in:
Zoe Roux 2025-05-02 02:12:08 +02:00
parent 71b3ee61af
commit 379765b28f
No known key found for this signature in database
11 changed files with 39 additions and 37 deletions

View File

@ -8,7 +8,7 @@
"blurhash": "^2.0.5", "blurhash": "^2.0.5",
"drizzle-kit": "^0.31.0", "drizzle-kit": "^0.31.0",
"drizzle-orm": "0.43.1", "drizzle-orm": "0.43.1",
"elysia": "^1.3.0-exp.71", "elysia": "^1.2.25",
"jose": "^6.0.10", "jose": "^6.0.10",
"parjs": "^1.3.9", "parjs": "^1.3.9",
"pg": "^8.15.6", "pg": "^8.15.6",
@ -165,16 +165,12 @@
"drizzle-orm": ["drizzle-orm@0.43.1", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-dUcDaZtE/zN4RV/xqGrVSMpnEczxd5cIaoDeor7Zst9wOe/HzC/7eAaulywWGYXdDEc9oBPMjayVEDg0ziTLJA=="], "drizzle-orm": ["drizzle-orm@0.43.1", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-dUcDaZtE/zN4RV/xqGrVSMpnEczxd5cIaoDeor7Zst9wOe/HzC/7eAaulywWGYXdDEc9oBPMjayVEDg0ziTLJA=="],
"elysia": ["elysia@1.3.0-exp.71", "", { "dependencies": { "@sinclair/typebox": "^0.34.33", "cookie": "^1.0.2", "exact-mirror": "0.1.1", "fast-decode-uri-component": "^1.0.1", "openapi-types": "^12.1.3" }, "peerDependencies": { "file-type": ">= 20.0.0", "typescript": ">= 5.0.0" }, "optionalPeers": ["file-type", "typescript"] }, "sha512-jL7z5OzJgs8pCzEXRmzzYu972S9hILiab7bVD3VBJHAE/9EMdG5uzxWA++3rxJXPEW7HvK3E31zaJKv6TtKgqA=="], "elysia": ["elysia@1.2.25", "", { "dependencies": { "@sinclair/typebox": "^0.34.27", "cookie": "^1.0.2", "memoirist": "^0.3.0", "openapi-types": "^12.1.3" }, "peerDependencies": { "typescript": ">= 5.0.0" }, "optionalPeers": ["typescript"] }, "sha512-WsdQpORJvb4uszzeqYT0lg97knw1iBW1NTzJ1Jm57tiHg+DfAotlWXYbjmvQ039ssV0fYELDHinLLoUazZkEHg=="],
"esbuild": ["esbuild@0.25.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.3", "@esbuild/android-arm": "0.25.3", "@esbuild/android-arm64": "0.25.3", "@esbuild/android-x64": "0.25.3", "@esbuild/darwin-arm64": "0.25.3", "@esbuild/darwin-x64": "0.25.3", "@esbuild/freebsd-arm64": "0.25.3", "@esbuild/freebsd-x64": "0.25.3", "@esbuild/linux-arm": "0.25.3", "@esbuild/linux-arm64": "0.25.3", "@esbuild/linux-ia32": "0.25.3", "@esbuild/linux-loong64": "0.25.3", "@esbuild/linux-mips64el": "0.25.3", "@esbuild/linux-ppc64": "0.25.3", "@esbuild/linux-riscv64": "0.25.3", "@esbuild/linux-s390x": "0.25.3", "@esbuild/linux-x64": "0.25.3", "@esbuild/netbsd-arm64": "0.25.3", "@esbuild/netbsd-x64": "0.25.3", "@esbuild/openbsd-arm64": "0.25.3", "@esbuild/openbsd-x64": "0.25.3", "@esbuild/sunos-x64": "0.25.3", "@esbuild/win32-arm64": "0.25.3", "@esbuild/win32-ia32": "0.25.3", "@esbuild/win32-x64": "0.25.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-qKA6Pvai73+M2FtftpNKRxJ78GIjmFXFxd/1DVBqGo/qNhLSfv+G12n9pNoWdytJC8U00TrViOwpjT0zgqQS8Q=="], "esbuild": ["esbuild@0.25.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.3", "@esbuild/android-arm": "0.25.3", "@esbuild/android-arm64": "0.25.3", "@esbuild/android-x64": "0.25.3", "@esbuild/darwin-arm64": "0.25.3", "@esbuild/darwin-x64": "0.25.3", "@esbuild/freebsd-arm64": "0.25.3", "@esbuild/freebsd-x64": "0.25.3", "@esbuild/linux-arm": "0.25.3", "@esbuild/linux-arm64": "0.25.3", "@esbuild/linux-ia32": "0.25.3", "@esbuild/linux-loong64": "0.25.3", "@esbuild/linux-mips64el": "0.25.3", "@esbuild/linux-ppc64": "0.25.3", "@esbuild/linux-riscv64": "0.25.3", "@esbuild/linux-s390x": "0.25.3", "@esbuild/linux-x64": "0.25.3", "@esbuild/netbsd-arm64": "0.25.3", "@esbuild/netbsd-x64": "0.25.3", "@esbuild/openbsd-arm64": "0.25.3", "@esbuild/openbsd-x64": "0.25.3", "@esbuild/sunos-x64": "0.25.3", "@esbuild/win32-arm64": "0.25.3", "@esbuild/win32-ia32": "0.25.3", "@esbuild/win32-x64": "0.25.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-qKA6Pvai73+M2FtftpNKRxJ78GIjmFXFxd/1DVBqGo/qNhLSfv+G12n9pNoWdytJC8U00TrViOwpjT0zgqQS8Q=="],
"esbuild-register": ["esbuild-register@3.6.0", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "esbuild": ">=0.12 <1" } }, "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg=="], "esbuild-register": ["esbuild-register@3.6.0", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "esbuild": ">=0.12 <1" } }, "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg=="],
"exact-mirror": ["exact-mirror@0.1.1", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-jygrs/z9JT3UBDVPsu4vLy8gqtTLTxVzoxLmDzkVXHizRGixDMdkdLF98ChZxsqHL0F7IcpTf8GUFRqa2qt3uw=="],
"fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="],
"get-tsconfig": ["get-tsconfig@4.10.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A=="], "get-tsconfig": ["get-tsconfig@4.10.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A=="],
"hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="], "hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="],
@ -183,6 +179,8 @@
"jose": ["jose@6.0.10", "", {}, "sha512-skIAxZqcMkOrSwjJvplIPYrlXGpxTPnro2/QWTDCxAdWQrSTV5/KqspMWmi5WAx5+ULswASJiZ0a+1B/Lxt9cw=="], "jose": ["jose@6.0.10", "", {}, "sha512-skIAxZqcMkOrSwjJvplIPYrlXGpxTPnro2/QWTDCxAdWQrSTV5/KqspMWmi5WAx5+ULswASJiZ0a+1B/Lxt9cw=="],
"memoirist": ["memoirist@0.3.0", "", {}, "sha512-wR+4chMgVPq+T6OOsk40u9Wlpw1Pjx66NMNiYxCQQ4EUJ7jDs3D9kTCeKdBOkvAiqXlHLVJlvYL01PvIJ1MPNg=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"node-addon-api": ["node-addon-api@8.3.1", "", {}, "sha512-lytcDEdxKjGJPTLEfW4mYMigRezMlyJY8W4wxJK8zE533Jlb8L8dRuObJFWg2P+AuOIxoCgKF+2Oq4d4Zd0OUA=="], "node-addon-api": ["node-addon-api@8.3.1", "", {}, "sha512-lytcDEdxKjGJPTLEfW4mYMigRezMlyJY8W4wxJK8zE533Jlb8L8dRuObJFWg2P+AuOIxoCgKF+2Oq4d4Zd0OUA=="],

View File

@ -13,7 +13,7 @@
"blurhash": "^2.0.5", "blurhash": "^2.0.5",
"drizzle-kit": "^0.31.0", "drizzle-kit": "^0.31.0",
"drizzle-orm": "0.43.1", "drizzle-orm": "0.43.1",
"elysia": "^1.3.0-exp.71", "elysia": "^1.2.25",
"jose": "^6.0.10", "jose": "^6.0.10",
"parjs": "^1.3.9", "parjs": "^1.3.9",
"pg": "^8.15.6", "pg": "^8.15.6",

View File

@ -143,13 +143,15 @@ export const entryVideosQ = db
export const mapProgress = ({ aliased }: { aliased: boolean }) => { export const mapProgress = ({ aliased }: { aliased: boolean }) => {
const { time, percent, playedDate, videoId } = getColumns(entryProgressQ); const { time, percent, playedDate, videoId } = getColumns(entryProgressQ);
const ret = { const ret = {
time: coalesce(time, sql`0`), time: coalesce(time, sql<number>`0`),
percent: coalesce(percent, sql`0`), percent: coalesce(percent, sql<number>`0`),
playedDate: sql`${playedDate}`, playedDate: sql<string>`${playedDate}`,
videoId: sql`${videoId}`, videoId: sql<string>`${videoId}`,
}; };
if (!aliased) return ret; if (!aliased) return ret;
return Object.fromEntries(Object.entries(ret).map(([k, v]) => [k, v.as(k)])); return Object.fromEntries(
Object.entries(ret).map(([k, v]) => [k, v.as(k)]),
) as unknown as typeof ret;
}; };
export async function getEntries({ export async function getEntries({
@ -197,7 +199,7 @@ export async function getEntries({
videos: entryVideosQ.videos, videos: entryVideosQ.videos,
progress: mapProgress({ aliased: true }), progress: mapProgress({ aliased: true }),
// specials don't have an `episodeNumber` but a `number` field. // specials don't have an `episodeNumber` but a `number` field.
number: episodeNumber, number: sql<number>`${episodeNumber}`,
// merge `extraKind` into `kind` // merge `extraKind` into `kind`
kind: sql<EntryKind>`case when ${kind} = 'extra' then ${extraKind} else ${kind}::text end`.as( kind: sql<EntryKind>`case when ${kind} = 'extra' then ${extraKind} else ${kind}::text end`.as(

View File

@ -18,6 +18,7 @@ import {
processLanguages, processLanguages,
} from "~/models/utils"; } from "~/models/utils";
import { desc } from "~/models/utils/descriptions"; import { desc } from "~/models/utils/descriptions";
import type { WatchlistStatus } from "~/models/watchlist";
import { import {
entryFilters, entryFilters,
entryProgressQ, entryProgressQ,
@ -186,12 +187,12 @@ export const historyH = new Elysia({ tags: ["profiles"] })
.select( .select(
db db
.select({ .select({
profilePk: sql`${profilePk}`, profilePk: sql`${profilePk}`.as("profilePk"),
entryPk: entries.pk, entryPk: entries.pk,
videoPk: videos.pk, videoPk: videos.pk,
percent: sql`hist.percent`, percent: sql`hist.percent`.as("percent"),
time: sql`hist.time`, time: sql`hist.time`.as("time"),
playedDate: sql`hist.playedDate`, playedDate: sql`hist.playedDate`.as("playedDate"),
}) })
.from(hist) .from(hist)
.innerJoin(entries, valEqEntries) .innerJoin(entries, valEqEntries)
@ -248,9 +249,9 @@ export const historyH = new Elysia({ tags: ["profiles"] })
.select( .select(
db db
.select({ .select({
profilePk: sql`${profilePk}`, profilePk: sql`${profilePk}`.as("profilePk"),
showPk: entries.showPk, showPk: entries.showPk,
status: sql` status: sql<WatchlistStatus>`
case case
when when
hist.percent >= 95 hist.percent >= 95
@ -258,35 +259,35 @@ export const historyH = new Elysia({ tags: ["profiles"] })
then 'completed'::watchlist_status then 'completed'::watchlist_status
else 'watching'::watchlist_status else 'watching'::watchlist_status
end end
`, `.as("status"),
seenCount: sql` seenCount: sql`
case case
when ${entries.kind} = 'movie' then hist.percent when ${entries.kind} = 'movie' then hist.percent
when hist.percent >= 95 then 1 when hist.percent >= 95 then 1
else 0 else 0
end end
`, `.as("seen_count"),
nextEntry: sql` nextEntry: sql`
case case
when hist.percent >= 95 then ${nextEntryQ.pk} when hist.percent >= 95 then ${nextEntryQ.pk}
else ${entries.pk} else ${entries.pk}
end end
`, `.as("next_entry"),
score: sql`null`, score: sql`null`.as("score"),
startedAt: sql`hist.playedDate`, startedAt: sql`hist.playedDate`.as("startedAt"),
lastPlayedAt: sql`hist.playedDate`, lastPlayedAt: sql`hist.playedDate`.as("lastPlayedAt"),
completedAt: sql` completedAt: sql`
case case
when ${nextEntryQ.pk} is null then hist.playedDate when ${nextEntryQ.pk} is null then hist.playedDate
else null else null
end end
`, `.as("completedAt"),
// see https://github.com/drizzle-team/drizzle-orm/issues/3608 // see https://github.com/drizzle-team/drizzle-orm/issues/3608
updatedAt: sql`now()`, updatedAt: sql`now()`.as("updatedAt"),
}) })
.from(hist) .from(hist)
.leftJoin(entries, valEqEntries) .leftJoin(entries, valEqEntries)
.crossJoinLateral(nextEntryQ), .leftJoinLateral(nextEntryQ, sql`true`),
) )
.onConflictDoUpdate({ .onConflictDoUpdate({
target: [watchlist.profilePk, watchlist.showPk], target: [watchlist.profilePk, watchlist.showPk],

View File

@ -90,6 +90,7 @@ export const nextup = new Elysia({ tags: ["profiles"] })
seasonNumber, seasonNumber,
episodeNumber, episodeNumber,
extraKind, extraKind,
kind,
...entryCol ...entryCol
} = getColumns(entries); } = getColumns(entries);
@ -100,9 +101,10 @@ export const nextup = new Elysia({ tags: ["profiles"] })
videos: entryVideosQ.videos, videos: entryVideosQ.videos,
progress: mapProgress({ aliased: true }), progress: mapProgress({ aliased: true }),
// specials don't have an `episodeNumber` but a `number` field. // specials don't have an `episodeNumber` but a `number` field.
number: episodeNumber, number: sql<number>`${episodeNumber}`,
// assign more restrained types to make typescript happy. // assign more restrained types to make typescript happy.
kind: sql<Entry["kind"]>`${kind}`,
externalId: sql<any>`${externalId}`, externalId: sql<any>`${externalId}`,
order: sql<number>`${order}`, order: sql<number>`${order}`,
seasonNumber: sql<number>`${seasonNumber}`, seasonNumber: sql<number>`${seasonNumber}`,

View File

@ -3,7 +3,6 @@ import { Episode, SeedEpisode } from "./episode";
import type { Extra } from "./extra"; import type { Extra } from "./extra";
import { MovieEntry, SeedMovieEntry } from "./movie-entry"; import { MovieEntry, SeedMovieEntry } from "./movie-entry";
import { SeedSpecial, Special } from "./special"; import { SeedSpecial, Special } from "./special";
import type { UnknownEntry } from "./unknown-entry";
export const Entry = t.Union([Episode, MovieEntry, Special]); export const Entry = t.Union([Episode, MovieEntry, Special]);
export type Entry = Episode | MovieEntry | Special; export type Entry = Episode | MovieEntry | Special;
@ -11,7 +10,7 @@ export type Entry = Episode | MovieEntry | Special;
export const SeedEntry = t.Union([SeedEpisode, SeedMovieEntry, SeedSpecial]); export const SeedEntry = t.Union([SeedEpisode, SeedMovieEntry, SeedSpecial]);
export type SeedEntry = SeedEpisode | SeedMovieEntry | SeedSpecial; export type SeedEntry = SeedEpisode | SeedMovieEntry | SeedSpecial;
export type EntryKind = Entry["kind"] | Extra["kind"] | UnknownEntry["kind"]; export type EntryKind = Entry["kind"] | Extra["kind"];
export * from "./episode"; export * from "./episode";
export * from "./movie-entry"; export * from "./movie-entry";

View File

@ -3,7 +3,6 @@ import type { Video } from "../video";
export const dune1984Video: Video = { export const dune1984Video: Video = {
id: "d1a62b87-9cfd-4f9c-9ad7-21f9b7fa6290", id: "d1a62b87-9cfd-4f9c-9ad7-21f9b7fa6290",
slug: "dune-1984",
path: "/video/Dune_1984/Dune (1984).mkv", path: "/video/Dune_1984/Dune (1984).mkv",
rendering: "ea3a0f8f2f2c5b61a07f61e4e8d9f8e01b2b92bcbb6f5ed1151e1f61619c2c0f", rendering: "ea3a0f8f2f2c5b61a07f61e4e8d9f8e01b2b92bcbb6f5ed1151e1f61619c2c0f",
part: null, part: null,

View File

@ -3,7 +3,6 @@ import type { Video } from "~/models/video";
export const duneVideo: Video = { export const duneVideo: Video = {
id: "c9a0d02e-6b8e-4ac1-b431-45b022ec0708", id: "c9a0d02e-6b8e-4ac1-b431-45b022ec0708",
slug: "dune",
path: "/video/Dune/Dune (2021).mkv", path: "/video/Dune/Dune (2021).mkv",
rendering: "f1953a4fb58247efb6c15b76468b6a9d13b4155b02094863b1a4f0c3fbb6db58", rendering: "f1953a4fb58247efb6c15b76468b6a9d13b4155b02094863b1a4f0c3fbb6db58",
part: null, part: null,

View File

@ -4,7 +4,8 @@ import { Movie } from "./movie";
import { Serie } from "./serie"; import { Serie } from "./serie";
export const Show = t.Union([ export const Show = t.Union([
t.Composite([t.Object({ kind: t.Literal("movie") }), Movie]), t.Intersect([t.Object({ kind: t.Literal("movie") }), Movie]),
t.Composite([t.Object({ kind: t.Literal("serie") }), Serie]), t.Intersect([t.Object({ kind: t.Literal("serie") }), Serie]),
t.Composite([t.Object({ kind: t.Literal("collection") }), Collection]), t.Intersect([t.Object({ kind: t.Literal("collection") }), Collection]),
]); ]);
export type Show = typeof Show.static;

View File

@ -7,6 +7,7 @@ export const WatchlistStatus = t.UnionEnum([
"dropped", "dropped",
"planned", "planned",
]); ]);
export type WatchlistStatus = typeof WatchlistStatus.static;
export const SerieWatchStatus = t.Object({ export const SerieWatchStatus = t.Object({
status: WatchlistStatus, status: WatchlistStatus,

View File

@ -159,7 +159,7 @@ describe("Get movie", () => {
expect(body.videos).toBeArrayOfSize(bubble.videos!.length); expect(body.videos).toBeArrayOfSize(bubble.videos!.length);
expect(body.videos[0]).toMatchObject({ expect(body.videos[0]).toMatchObject({
path: bubbleVideo.path, path: bubbleVideo.path,
slug: bubbleVideo.slug, slug: bubble.slug,
version: bubbleVideo.version, version: bubbleVideo.version,
rendering: bubbleVideo.rendering, rendering: bubbleVideo.rendering,
part: bubbleVideo.part, part: bubbleVideo.part,