Release 0.5.1
This commit is contained in:
1 parent
5a9d7735d4
commit
fb24d171ae
36 files changed
+5099
-263
No files matched your search
@@ -9,10 +9,13 @@ import {
|
||||
addPieceUsage,
|
||||
addShowSection,
|
||||
createShow,
|
||||
duplicatePieceUsage,
|
||||
getPieceUsageContexts,
|
||||
getShow,
|
||||
listShowTemplates,
|
||||
movePieceUsage,
|
||||
moveShowSection,
|
||||
refreshPieceUsageFromOriginal,
|
||||
removePieceUsage,
|
||||
removeShowSection,
|
||||
updatePieceUsage,
|
||||
@@ -46,9 +49,16 @@ function editedUsage(usage: PieceUsage, overrides: Partial<PieceUsage> = {}) {
|
||||
moderation: edited.moderation,
|
||||
transition: edited.transition,
|
||||
presentation: edited.presentation,
|
||||
presentationMode: edited.presentationMode,
|
||||
presentationSourceLabel: edited.presentationSourceLabel,
|
||||
presentationSourceUrl: edited.presentationSourceUrl,
|
||||
presentationSourceTimecodeSeconds: edited.presentationSourceTimecodeSeconds,
|
||||
presentationTitle: edited.presentationTitle,
|
||||
presentationDescription: edited.presentationDescription,
|
||||
presentationImageUrl: edited.presentationImageUrl,
|
||||
presentationSiteName: edited.presentationSiteName,
|
||||
ownNotes: edited.ownNotes,
|
||||
durationSeconds: edited.durationSeconds,
|
||||
status: edited.status
|
||||
durationSeconds: edited.durationSeconds
|
||||
};
|
||||
}
|
||||
|
||||
@@ -153,6 +163,245 @@ describe('show builder data layer', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('derives sent history and current planning only from qualifying usages by original id', () => {
|
||||
const completed = createShow({
|
||||
title: 'Abgeschlossene Sendung',
|
||||
startsAtUtc: '2027-01-10T19:00:00Z',
|
||||
templateId: null
|
||||
});
|
||||
const completedSection = addShowSection(completed.id, completed.revision, {
|
||||
name: 'Ablauf',
|
||||
categoryId: null,
|
||||
explicitDurationSeconds: null
|
||||
});
|
||||
const done = addPieceUsage(completed.id, completedSection.revision, {
|
||||
pieceId: 'piece-console',
|
||||
sectionId: completedSection.id
|
||||
});
|
||||
const skipped = addPieceUsage(completed.id, done.revision, {
|
||||
pieceId: 'piece-effects',
|
||||
sectionId: completedSection.id
|
||||
});
|
||||
const reserve = addPieceUsage(completed.id, skipped.revision, {
|
||||
pieceId: 'piece-pixels',
|
||||
sectionId: null
|
||||
});
|
||||
getDatabase()
|
||||
.prepare("UPDATE piece_usages SET status = 'done' WHERE id IN (?, ?)")
|
||||
.run(done.id, reserve.id);
|
||||
getDatabase()
|
||||
.prepare("UPDATE piece_usages SET status = 'skipped' WHERE id = ?")
|
||||
.run(skipped.id);
|
||||
getDatabase().prepare("UPDATE shows SET status = 'completed' WHERE id = ?").run(completed.id);
|
||||
|
||||
const active = createShow({ title: 'Aktuelle Planung', startsAtUtc: null, templateId: null });
|
||||
const activeSection = addShowSection(active.id, active.revision, {
|
||||
name: 'Plan',
|
||||
categoryId: null,
|
||||
explicitDurationSeconds: null
|
||||
});
|
||||
const planned = addPieceUsage(active.id, activeSection.revision, {
|
||||
pieceId: 'piece-console',
|
||||
sectionId: activeSection.id
|
||||
});
|
||||
const plannedReserve = addPieceUsage(active.id, planned.revision, {
|
||||
pieceId: 'piece-effects',
|
||||
sectionId: null
|
||||
});
|
||||
getDatabase().prepare("UPDATE piece_usages SET status = 'done' WHERE id = ?").run(planned.id);
|
||||
|
||||
let contexts = getPieceUsageContexts(
|
||||
['piece-console', 'piece-effects', 'piece-pixels'],
|
||||
active.id
|
||||
);
|
||||
expect(contexts['piece-console']).toMatchObject({
|
||||
isPlannedInCurrentShow: true,
|
||||
sentHistory: [
|
||||
{
|
||||
usageId: done.id,
|
||||
showId: completed.id,
|
||||
showTitle: 'Abgeschlossene Sendung',
|
||||
usedAtUtc: '2027-01-10T19:00:00.000Z'
|
||||
}
|
||||
]
|
||||
});
|
||||
expect(contexts['piece-effects']).toEqual({
|
||||
isPlannedInActiveShow: true,
|
||||
isPlannedInCurrentShow: true,
|
||||
sentHistory: []
|
||||
});
|
||||
expect(contexts['piece-pixels']).toEqual({
|
||||
isPlannedInActiveShow: true,
|
||||
isPlannedInCurrentShow: false,
|
||||
sentHistory: []
|
||||
});
|
||||
|
||||
removePieceUsage(active.id, plannedReserve.id, plannedReserve.revision);
|
||||
contexts = getPieceUsageContexts(['piece-effects'], active.id);
|
||||
expect(contexts['piece-effects'].isPlannedInCurrentShow).toBe(false);
|
||||
});
|
||||
|
||||
it('duplicates an adjusted usage directly after its source without live measurements', () => {
|
||||
const created = createShow({
|
||||
title: 'Einplanungsduplikat',
|
||||
startsAtUtc: null,
|
||||
templateId: null
|
||||
});
|
||||
const section = addShowSection(created.id, created.revision, {
|
||||
name: 'Ablauf',
|
||||
categoryId: null,
|
||||
explicitDurationSeconds: null
|
||||
});
|
||||
const usage = addPieceUsage(created.id, section.revision, {
|
||||
pieceId: 'piece-console',
|
||||
sectionId: section.id
|
||||
});
|
||||
const source = getShow(created.id)!.sections[0].usages[0];
|
||||
const adjusted = updatePieceUsage(
|
||||
created.id,
|
||||
usage.id,
|
||||
usage.revision,
|
||||
editedUsage(source, {
|
||||
title: 'Angepasste Einplanung',
|
||||
durationSeconds: 777,
|
||||
intro: 'Eigene Einleitung',
|
||||
research: 'Eigene Recherche',
|
||||
moderation: 'Eigene Moderation',
|
||||
transition: 'Eigene Überleitung',
|
||||
presentation: 'Eigene Präsentation',
|
||||
ownNotes: 'Eigene Notizen'
|
||||
})
|
||||
);
|
||||
getDatabase().prepare("UPDATE piece_usages SET status = 'done' WHERE id = ?").run(usage.id);
|
||||
getDatabase()
|
||||
.prepare(
|
||||
`INSERT INTO live_usage_runs (id, show_id, usage_id, started_at_utc, ended_at_utc)
|
||||
VALUES ('source-run', ?, ?, '2027-01-01T10:00:00.000Z', '2027-01-01T10:10:00.000Z')`
|
||||
)
|
||||
.run(created.id, usage.id);
|
||||
const pieceCountBefore = (
|
||||
getDatabase().prepare('SELECT count(*) AS count FROM pieces').get() as { count: number }
|
||||
).count;
|
||||
|
||||
const duplicate = duplicatePieceUsage(created.id, usage.id, adjusted.revision);
|
||||
const usages = getShow(created.id)!.sections[0].usages;
|
||||
expect(usages.map((item) => item.id)).toEqual([usage.id, duplicate.id]);
|
||||
expect(usages[1]).toMatchObject({
|
||||
originalPieceId: 'piece-console',
|
||||
status: 'open',
|
||||
durationSeconds: 777,
|
||||
title: 'Angepasste Einplanung',
|
||||
intro: 'Eigene Einleitung',
|
||||
research: 'Eigene Recherche',
|
||||
moderation: 'Eigene Moderation',
|
||||
transition: 'Eigene Überleitung',
|
||||
presentation: 'Eigene Präsentation',
|
||||
ownNotes: 'Eigene Notizen'
|
||||
});
|
||||
expect(
|
||||
getDatabase()
|
||||
.prepare('SELECT count(*) AS count FROM live_usage_runs WHERE usage_id = ?')
|
||||
.get(duplicate.id)
|
||||
).toEqual({ count: 0 });
|
||||
expect(
|
||||
(getDatabase().prepare('SELECT count(*) AS count FROM pieces').get() as { count: number })
|
||||
.count
|
||||
).toBe(pieceCountBefore);
|
||||
|
||||
getDatabase()
|
||||
.prepare(
|
||||
`UPDATE pieces SET title = 'Aktuelles Original', duration_seconds = 420,
|
||||
intro = 'Neue Einleitung', research_text = 'Neue Recherche',
|
||||
moderation_notes = 'Neue Moderation', transition_text = 'Neue Überleitung',
|
||||
presentation_text = 'Neue Präsentation' WHERE id = 'piece-console'`
|
||||
)
|
||||
.run();
|
||||
const refreshed = refreshPieceUsageFromOriginal(created.id, usage.id, duplicate.revision);
|
||||
const refreshedUsages = getShow(created.id)!.sections[0].usages;
|
||||
expect(refreshedUsages[0]).toMatchObject({
|
||||
title: 'Aktuelles Original',
|
||||
durationSeconds: 420,
|
||||
intro: 'Neue Einleitung',
|
||||
research: 'Neue Recherche',
|
||||
moderation: 'Neue Moderation',
|
||||
transition: 'Neue Überleitung',
|
||||
presentation: 'Neue Präsentation',
|
||||
ownNotes: 'Eigene Notizen',
|
||||
status: 'done'
|
||||
});
|
||||
expect(refreshedUsages[1]).toMatchObject({
|
||||
title: 'Angepasste Einplanung',
|
||||
durationSeconds: 777,
|
||||
status: 'open'
|
||||
});
|
||||
expect(
|
||||
getDatabase()
|
||||
.prepare('SELECT count(*) AS count FROM live_usage_runs WHERE usage_id = ?')
|
||||
.get(usage.id)
|
||||
).toEqual({ count: 1 });
|
||||
expect(() => duplicatePieceUsage(created.id, usage.id, adjusted.revision)).toThrow('CONFLICT');
|
||||
expect(getShow(created.id)?.revision).toBe(refreshed.revision);
|
||||
getDatabase()
|
||||
.prepare('UPDATE piece_usages SET original_piece_id = NULL WHERE id = ?')
|
||||
.run(duplicate.id);
|
||||
expect(() =>
|
||||
refreshPieceUsageFromOriginal(created.id, duplicate.id, refreshed.revision)
|
||||
).toThrow('ORIGINAL_NOT_FOUND');
|
||||
expect(getShow(created.id)?.revision).toBe(refreshed.revision);
|
||||
expect(getShow(created.id)!.sections[0].usages).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('snapshots presentation settings until an explicit refresh and copies them on duplication', () => {
|
||||
getDatabase()
|
||||
.prepare(
|
||||
`UPDATE piece_sources SET label = 'YouTube', url = ?, timecode_seconds = 75
|
||||
WHERE piece_id = 'piece-console' AND position = 0`
|
||||
)
|
||||
.run('https://youtu.be/dQw4w9WgXcQ');
|
||||
getDatabase()
|
||||
.prepare(
|
||||
`UPDATE pieces SET presentation_mode = 'video', presentation_source_position = 0,
|
||||
presentation_title = 'Gespeicherte Vorschau', presentation_description = 'Erster Stand',
|
||||
presentation_site_name = 'YouTube', presentation_provider = 'youtube',
|
||||
presentation_provider_id = 'dQw4w9WgXcQ' WHERE id = 'piece-console'`
|
||||
)
|
||||
.run();
|
||||
const show = createShow({ title: 'Snapshot-Test', startsAtUtc: null, templateId: null });
|
||||
const section = addShowSection(show.id, show.revision, {
|
||||
name: 'Videos',
|
||||
categoryId: null,
|
||||
explicitDurationSeconds: null
|
||||
});
|
||||
const added = addPieceUsage(show.id, section.revision, {
|
||||
pieceId: 'piece-console',
|
||||
sectionId: section.id
|
||||
});
|
||||
let usage = getShow(show.id)!.sections[0].usages[0];
|
||||
expect(usage).toMatchObject({
|
||||
presentationMode: 'video',
|
||||
presentationSourceUrl: 'https://youtu.be/dQw4w9WgXcQ',
|
||||
presentationSourceTimecodeSeconds: 75,
|
||||
presentationTitle: 'Gespeicherte Vorschau'
|
||||
});
|
||||
|
||||
getDatabase()
|
||||
.prepare(
|
||||
`UPDATE pieces SET presentation_title = 'Neuer Originaltitel' WHERE id = 'piece-console'`
|
||||
)
|
||||
.run();
|
||||
expect(getShow(show.id)!.sections[0].usages[0].presentationTitle).toBe('Gespeicherte Vorschau');
|
||||
const refreshed = refreshPieceUsageFromOriginal(show.id, usage.id, added.revision);
|
||||
usage = getShow(show.id)!.sections[0].usages[0];
|
||||
expect(usage.presentationTitle).toBe('Neuer Originaltitel');
|
||||
duplicatePieceUsage(show.id, usage.id, refreshed.revision);
|
||||
expect(getShow(show.id)!.sections[0].usages[1]).toMatchObject({
|
||||
presentationMode: 'video',
|
||||
presentationSourceUrl: 'https://youtu.be/dQw4w9WgXcQ',
|
||||
presentationTitle: 'Neuer Originaltitel',
|
||||
status: 'open'
|
||||
});
|
||||
});
|
||||
|
||||
it('persists normalized section and usage reordering, moving and removal', () => {
|
||||
const created = createShow({ title: 'Sortierung', startsAtUtc: null, templateId: null });
|
||||
const firstSection = addShowSection(created.id, created.revision, {
|
||||
|
||||
Reference in new issue
Block a user