teqo-backup/tests/unit/contentPieceCatalog.unit.spec.ts

602 lines
23 KiB
TypeScript

// @vitest-environment node
import { describe, expect, it } from 'vitest'
import {
buildContentPieceCatalogHref,
cardCatalogItems,
CONTENT_PIECE_ASPECT_CLASS,
contentCatalogItems,
contentPieceAspectClass,
contentPieceBoardBucket,
contentPieceCatalogActiveFilters,
contentPieceCatalogFacets,
contentPieceDownloadFilename,
contentPieceFramePath,
contentPieceMediaKind,
contentPieceMediaPath,
contentPiecePublicPath,
contentPiecePublicPeople,
contentPieceTextExcerpt,
contentPieceThemeMatch,
contentPieceThemeTerms,
filterContentPieceCatalogItems,
interleaveCatalogItems,
isCardCatalogItem,
parseContentPieceCatalogParams,
toContentPiecePublicItem,
type CardCatalogItem,
type ContentCatalogItem,
type ContentPiecePublicItem,
type ContentPiecePublicSource,
} from '@/lib/contentPieceCatalog'
const source = (patch: Partial<ContentPiecePublicSource> = {}): ContentPiecePublicSource => ({
id: 1,
slug: 'fim-da-escala-6x1',
title: 'Fim da escala 6x1 é saúde',
type: 'video',
status: 'publicado',
origin: 'arquivo',
topics: ['economia-trabalho'],
cityLabel: 'Salvador',
region: 'Metropolitano de Salvador',
description: 'Solla explica a jornada.',
transcript: 'A redução da jornada é saúde.',
durationSeconds: 134,
searchText: 'fim da escala 6x1 e saude salvador',
media: { id: 7, filename: 'Fim da Escala.MP4' },
...patch,
})
const item = (patch: Partial<ContentPiecePublicSource> = {}): ContentPiecePublicItem => {
const resolved = toContentPiecePublicItem(source(patch))
if (!resolved) throw new Error('fixture should be public')
return resolved
}
const pieceRows = (rows: readonly ContentCatalogItem[]): ContentPiecePublicItem[] =>
rows.filter((row): row is ContentPiecePublicItem => !isCardCatalogItem(row))
const cardRows = (rows: readonly ContentCatalogItem[]): CardCatalogItem[] =>
rows.filter((row): row is CardCatalogItem => isCardCatalogItem(row))
describe('content piece catalog params', () => {
it('parses one valid value per facet and drops unknown values', () => {
expect(
parseContentPieceCatalogParams({
tipo: ['video', 'foto'],
cidade: 'salvador',
regiao: 'metropolitano-de-salvador',
tema: 'economia-trabalho',
instituicao: 'camara-dos-deputados',
lideranca: 'maria-silva',
q: ' escala ',
mode: 'tema',
}),
).toEqual({
tipo: 'video',
cidade: 'salvador',
regiao: 'metropolitano-de-salvador',
tema: 'economia-trabalho',
instituicao: 'camara-dos-deputados',
lideranca: 'maria-silva',
q: 'escala',
mode: 'tema',
})
expect(parseContentPieceCatalogParams({ tipo: 'inexistente', tema: 'nada' })).toEqual({
tipo: null,
cidade: null,
regiao: null,
tema: null,
instituicao: null,
lideranca: null,
q: '',
mode: null,
})
expect(parseContentPieceCatalogParams({ cidade: '../etc/passwd' }).cidade).toBeNull()
// S37 — the people facet is a display-name slug; spaces/accents never pass.
expect(parseContentPieceCatalogParams({ lideranca: 'Maria Silva' }).lideranca).toBeNull()
expect(parseContentPieceCatalogParams({}).q).toBe('')
})
it('parses the theme mode only with a query and drops unknown modes', () => {
expect(parseContentPieceCatalogParams({ q: 'escala', mode: 'tema' }).mode).toBe('tema')
// `exato` is the default and never serialized; unknown values mean exact too.
expect(parseContentPieceCatalogParams({ q: 'escala', mode: 'exato' }).mode).toBeNull()
expect(parseContentPieceCatalogParams({ q: 'escala', mode: 'semantico' }).mode).toBeNull()
expect(parseContentPieceCatalogParams({ q: ' ', mode: 'tema' }).mode).toBeNull()
expect(parseContentPieceCatalogParams({ mode: 'tema' }).mode).toBeNull()
})
it('builds the canonical href with a fixed order and no empty params', () => {
expect(buildContentPieceCatalogHref({})).toBe('/conteudos')
expect(buildContentPieceCatalogHref({ q: ' ' })).toBe('/conteudos')
expect(
buildContentPieceCatalogHref({
q: 'escala',
tema: 'economia-trabalho',
tipo: 'video',
cidade: 'salvador',
}),
).toBe('/conteudos?tipo=video&cidade=salvador&tema=economia-trabalho&q=escala')
expect(buildContentPieceCatalogHref({ tipo: 'video', tema: null, q: '' })).toBe(
'/conteudos?tipo=video',
)
// S37 — the people facet is appended after Instituição, before the query.
expect(
buildContentPieceCatalogHref({
instituicao: 'camara-dos-deputados',
lideranca: 'maria-silva',
q: 'escala',
}),
).toBe('/conteudos?instituicao=camara-dos-deputados&lideranca=maria-silva&q=escala')
expect(buildContentPieceCatalogHref({ q: 'escala', mode: 'tema' })).toBe(
'/conteudos?q=escala&mode=tema',
)
// A mode without a query has nothing to expand and is not serialized.
expect(buildContentPieceCatalogHref({ q: '', mode: 'tema' })).toBe('/conteudos')
})
it('lists the active filters with their removal hrefs', () => {
const items = [
item(),
item({ id: 2, slug: 'card', type: 'card', topics: ['saude'], cityLabel: null }),
]
const params = parseContentPieceCatalogParams({
tipo: 'video',
cidade: 'salvador',
q: 'escala',
})
const filters = contentPieceCatalogActiveFilters(params, contentPieceCatalogFacets(items))
expect(filters.map((filter) => `${filter.label}: ${filter.value}`)).toEqual([
'Tipo: Vídeo',
'Cidade: Salvador',
'Busca: escala',
])
expect(filters[0]?.removeHref).toBe('/conteudos?cidade=salvador&q=escala')
expect(filters[2]?.removeHref).toBe('/conteudos?tipo=video&cidade=salvador')
})
})
describe('content piece catalog facets', () => {
it('derives options from published items only, in enum/alphabetical order', () => {
const facets = contentPieceCatalogFacets([
item({ leaderNames: ['Maria Silva'] }),
item({
id: 2,
slug: 'card-saude',
type: 'card',
topics: ['saude'],
cityLabel: 'Feira de Santana',
region: 'Portal do Sertão',
institution: 'Câmara dos Deputados',
publicFigures: ['Dep. Estadual Exemplo'],
}),
])
expect(facets.tipo).toEqual([
{ value: 'video', label: 'Vídeo' },
{ value: 'card', label: 'Card' },
])
expect(facets.tema.map((option) => option.label)).toEqual(['Saúde', 'Economia e Trabalho'])
expect(facets.cidade.map((option) => option.label)).toEqual(['Feira de Santana', 'Salvador'])
expect(facets.regiao.map((option) => option.label)).toEqual([
'Metropolitano de Salvador',
'Portal do Sertão',
])
expect(facets.instituicao).toEqual([
{ value: 'camara-dos-deputados', label: 'Câmara dos Deputados' },
])
// S37 — one facet unions the leader snapshot and the curated figures,
// alphabetical by the display name.
expect(facets.lideranca).toEqual([
{ value: 'dep-estadual-exemplo', label: 'Dep. Estadual Exemplo' },
{ value: 'maria-silva', label: 'Maria Silva' },
])
})
it('collapses homonyms and spelling variants into one people option (S37)', () => {
const facets = contentPieceCatalogFacets([
item({ leaderNames: ['Dra. Elaine'] }),
item({ id: 2, slug: 'outra', publicFigures: ['Dra Elaine'] }),
])
expect(facets.lideranca).toEqual([{ value: 'dra-elaine', label: 'Dra Elaine' }])
// No piece carries people: the facet renders nothing.
expect(contentPieceCatalogFacets([item()]).lideranca).toEqual([])
})
})
describe('content piece catalog filtering', () => {
const items = [
item(),
item({
id: 2,
slug: 'card-feira',
title: 'Card do giro',
type: 'card',
topics: ['saude'],
cityLabel: 'Feira de Santana',
searchText: 'card do giro saude feira de santana',
}),
item({
id: 3,
slug: 'link-yt',
origin: 'youtube',
media: null,
sourceUrl: 'https://youtu.be/X',
searchText: 'video no youtube',
}),
]
const idsFor = (raw: Record<string, string>) =>
pieceRows(filterContentPieceCatalogItems(items, parseContentPieceCatalogParams(raw))).map(
(row) => row.id,
)
it('combines facets and the accent-insensitive term', () => {
expect(idsFor({})).toEqual([1, 2, 3])
expect(idsFor({ tipo: 'video' })).toEqual([1, 3])
expect(idsFor({ tema: 'saude' })).toEqual([2])
expect(idsFor({ cidade: 'feira-de-santana' })).toEqual([2])
expect(idsFor({ q: 'ESCALA' })).toEqual([1])
expect(idsFor({ q: 'saúde' })).toEqual([1, 2])
expect(idsFor({ tipo: 'video', cidade: 'feira-de-santana' })).toEqual([])
expect(idsFor({ tipo: 'video', q: 'escala' })).toEqual([1])
})
it('filters by the people facet union and combines with the other facets (S37)', () => {
const people = [
item({ leaderNames: ['Maria Silva'] }),
item({
id: 2,
slug: 'card-feira',
type: 'card',
cityLabel: 'Feira de Santana',
publicFigures: ['Dep. Estadual Exemplo'],
}),
]
const ids = (raw: Record<string, string>) =>
pieceRows(filterContentPieceCatalogItems(people, parseContentPieceCatalogParams(raw))).map(
(row) => row.id,
)
expect(ids({ lideranca: 'maria-silva' })).toEqual([1])
expect(ids({ lideranca: 'dep-estadual-exemplo' })).toEqual([2])
expect(ids({ lideranca: 'maria-silva', tipo: 'card' })).toEqual([])
expect(ids({ lideranca: 'ninguem' })).toEqual([])
})
it('ORs the literal query with the expanded theme terms, facets still AND', () => {
const idsForTerms = (raw: Record<string, string>, terms: string[]) =>
pieceRows(
filterContentPieceCatalogItems(items, parseContentPieceCatalogParams(raw), terms),
).map((row) => row.id)
// No terms is byte-identical to the literal search.
expect(idsForTerms({ q: 'escala' }, [])).toEqual([1])
// The expanded term surfaces a piece the query alone would not.
expect(idsForTerms({ q: 'giro' }, ['card'])).toEqual([2])
expect(idsForTerms({ q: 'zzz' }, ['card', 'giro'])).toEqual([2])
// Facets keep narrowing the OR result.
expect(idsForTerms({ q: 'zzz', tipo: 'video' }, ['card'])).toEqual([])
expect(idsForTerms({ q: 'zzz' }, ['ESCALA'])).toEqual([1])
})
it('mixes the card items into the same facet and order (S38)', () => {
const rows = filterContentPieceCatalogItems(
[...items, ...cardCatalogItems()],
parseContentPieceCatalogParams({ tipo: 'card' }),
)
expect(rows.map((row) => (isCardCatalogItem(row) ? row.modelId : row.slug))).toEqual([
'card-feira',
...cardCatalogItems().map((card) => card.modelId),
])
expect(contentPieceCatalogFacets([...items, ...cardCatalogItems()]).tipo).toEqual([
{ value: 'video', label: 'Vídeo' },
{ value: 'card', label: 'Card' },
])
})
})
describe('card models as catalogue items (S38)', () => {
const cards = cardCatalogItems()
const modelIdsFor = (raw: Record<string, string>) =>
cardRows(filterContentPieceCatalogItems(cards, parseContentPieceCatalogParams(raw))).map(
(card) => card.modelId,
)
it('builds one item per model with the studio deep link and the model copy', () => {
expect(cards.map((card) => card.modelId)).toEqual([
'eu-sou-solla',
'perfil-quadrado',
'perfil-retangular',
'time-de-voce',
'time-do-estadual',
'minha-colinha',
])
expect(cards.every((card) => card.href === `/cards?model=${card.modelId}`)).toBe(true)
expect(cards.find((card) => card.modelId === 'minha-colinha')?.badge).toBe('NOVO')
expect(cards.every((card) => card.badge === null || card.modelId === 'minha-colinha')).toBe(
true,
)
})
it('carries the real art, or the neutral placeholder of the photo models', () => {
const artOf = (modelId: string) => cards.find((card) => card.modelId === modelId)?.art
expect(artOf('time-de-voce')).toEqual({ kind: 'image', src: '/cards/team-card-example.jpg' })
expect(artOf('eu-sou-solla')).toEqual({ kind: 'image', src: '/cards/name-card-base.jpg' })
expect(artOf('perfil-quadrado')).toEqual({ kind: 'placeholder', shape: 'square' })
expect(artOf('perfil-retangular')).toEqual({ kind: 'placeholder', shape: 'portrait' })
})
it('finds the models by nickname, by name and by description', () => {
expect(modelIdsFor({ q: 'santinho' })).toEqual(['time-de-voce', 'minha-colinha'])
expect(modelIdsFor({ q: 'foto de perfil' })).toEqual(['perfil-quadrado', 'perfil-retangular'])
expect(modelIdsFor({ q: 'colinha' })).toEqual(['minha-colinha'])
expect(modelIdsFor({ q: 'estadual' })).toEqual(['time-do-estadual'])
expect(modelIdsFor({ q: 'Moldura quadrada' })).toEqual(['perfil-quadrado'])
expect(modelIdsFor({ q: 'cola de votação' })).toEqual(['minha-colinha'])
})
it('matches the Tipo facet only as card and never a geographic/theme facet', () => {
expect(modelIdsFor({ tipo: 'card' })).toHaveLength(6)
expect(modelIdsFor({ tipo: 'video' })).toEqual([])
expect(modelIdsFor({ cidade: 'salvador' })).toEqual([])
expect(modelIdsFor({ regiao: 'metropolitano-de-salvador' })).toEqual([])
expect(modelIdsFor({ instituicao: 'camara-dos-deputados' })).toEqual([])
expect(modelIdsFor({ tema: 'saude' })).toEqual([])
})
it('feeds only the Tipo facet, never the geographic ones', () => {
const facets = contentPieceCatalogFacets(cards)
expect(facets.tipo).toEqual([{ value: 'card', label: 'Card' }])
expect(facets.cidade).toEqual([])
expect(facets.regiao).toEqual([])
expect(facets.instituicao).toEqual([])
expect(facets.tema).toEqual([])
})
it('appends the cards only to a non-empty Central', () => {
expect(contentCatalogItems([], 0, cards)).toEqual([])
expect(contentCatalogItems([item()], 1, cards)).toHaveLength(7)
expect(contentCatalogItems([item()], 1, cards).map((row) => isCardCatalogItem(row))).toEqual([
false,
true,
true,
true,
true,
true,
true,
])
})
it('alternates pieces and models, never a block of cards', () => {
const label = (row: ContentCatalogItem) => (isCardCatalogItem(row) ? row.modelId : row.id)
const first = item({ id: 1 })
const second = item({ id: 2, slug: 'outra-peca' })
expect(interleaveCatalogItems([first, second], cards.slice(0, 2)).map(label)).toEqual([
1,
'eu-sou-solla',
2,
'perfil-quadrado',
])
// The remainder of the longer side keeps its own committed order.
expect(interleaveCatalogItems([first], cards.slice(0, 2)).map(label)).toEqual([
1,
'eu-sou-solla',
'perfil-quadrado',
])
expect(interleaveCatalogItems([first, second], []).map(label)).toEqual([1, 2])
})
})
describe('content piece theme terms and provenance (S28)', () => {
it('drops empty terms, duplicates and the literal query itself', () => {
expect(contentPieceThemeTerms('escala', ['', ' ', 'Escala', 'fim da escala 6x1'])).toEqual([
'fim da escala 6x1',
])
expect(contentPieceThemeTerms('escala', ['FIM DA ESCALA', 'fim da escala'])).toEqual([
'FIM DA ESCALA',
])
})
it('quotes the transcript window that carries the term', () => {
const match = contentPieceThemeMatch(source({ searchText: 'a reducao da jornada e saude' }), [
'redução da jornada',
])
expect(match?.term).toBe('redução da jornada')
expect(match?.evidence?.quoted).toBe(true)
expect(match?.evidence?.source).toBe('transcript')
expect(match?.evidence?.parts.some((part) => part.highlighted)).toBe(true)
})
it('falls back to the description window when the transcript does not carry it', () => {
const match = contentPieceThemeMatch(
source({
transcript: null,
description: 'Solla explica a jornada de trabalho.',
searchText: 'jornada de trabalho',
}),
['jornada de trabalho'],
)
expect(match?.evidence?.source).toBe('description')
expect(match?.evidence?.quoted).toBe(true)
})
it('shows a real passage without highlight when the match came from elsewhere', () => {
const match = contentPieceThemeMatch(
source({ searchText: 'camara dos deputados saude', description: 'Solla explica a jornada.' }),
['camara dos deputados'],
)
expect(match?.term).toBe('camara dos deputados')
expect(match?.evidence?.quoted).toBe(false)
expect(match?.evidence?.source).toBe('description')
expect(match?.evidence?.parts).toEqual([
{ text: 'Solla explica a jornada.', highlighted: false },
])
})
it('never claims a term the haystack does not carry', () => {
expect(contentPieceThemeMatch(source(), ['escola sem partido'])).toBeNull()
expect(contentPieceThemeMatch(source(), [])).toBeNull()
})
it('annotates the public item only when the theme mode passes terms', () => {
const themed = source({ searchText: 'a reducao da jornada e saude' })
const item = toContentPiecePublicItem(themed, { themeTerms: ['redução da jornada'] })
expect(item?.themeMatch?.term).toBe('redução da jornada')
const literal = toContentPiecePublicItem(themed)
expect(literal?.themeMatch).toBeNull()
// The transcript itself never reaches the view model.
expect(literal && 'transcript' in literal).toBe(false)
})
})
describe('content piece public view model', () => {
it('resolves the file piece with paths, duration and metadata line', () => {
const view = item()
expect(view.isLink).toBe(false)
expect(view.typeLabel).toBe('Vídeo')
expect(view.durationLabel).toBe('02:14')
expect(view.metaLabel).toBe('Economia e Trabalho · Salvador')
expect(view.publicPath).toBe('/conteudos/fim-da-escala-6x1')
expect(view.file?.path).toBe('/conteudos/fim-da-escala-6x1/midia')
expect(view.file?.mimeType).toBeNull()
expect(view.file?.downloadFilename).toBe('jorge-solla-1313-fim-da-escala-6x1.mp4')
})
it('resolves a link piece with the platform URL and no media', () => {
const view = item({
origin: 'instagram',
media: null,
sourceUrl: 'https://www.instagram.com/reel/ABC/',
transcript: null,
})
expect(view.isLink).toBe(true)
expect(view.originLabel).toBe('Instagram')
expect(view.file).toBeNull()
})
it('declares a frame only for a video with an archived file (C226)', () => {
expect(item().framePath).toBe('/conteudos/fim-da-escala-6x1/frame')
// Same decision as the card's own media kind, so a piece can never be asked
// for a frame it cannot produce.
expect(item({ media: { id: 7, mimeType: 'video/mp4' }, type: 'texto' }).framePath).toBe(
'/conteudos/fim-da-escala-6x1/frame',
)
// Everything else keeps the neutral slot.
expect(item({ media: { id: 7, mimeType: 'audio/mpeg' }, type: 'audio' }).framePath).toBeNull()
expect(item({ media: { id: 7, mimeType: 'image/png' }, type: 'foto' }).framePath).toBeNull()
expect(item({ media: { id: 7, mimeType: 'text/plain' }, type: 'texto' }).framePath).toBeNull()
expect(
item({ media: { id: 7, mimeType: 'application/pdf' }, type: 'card' }).framePath,
).toBeNull()
// Unknown MIME with a non-video editorial type: the type decides, as everywhere.
expect(item({ media: { id: 7 }, type: 'foto' }).framePath).toBeNull()
// A link piece never has an archived file, so it never has a frame.
expect(
item({ media: null, sourceUrl: 'https://www.instagram.com/reel/ABC/', type: 'video' })
.framePath,
).toBeNull()
})
it('resolves the media kind from the stored MIME with a type fallback', () => {
expect(contentPieceMediaKind(item({ media: { id: 7, mimeType: 'video/mp4' } }))).toBe('video')
expect(contentPieceMediaKind(item({ media: { id: 7, mimeType: 'audio/mpeg' } }))).toBe('audio')
expect(contentPieceMediaKind(item({ media: { id: 7, mimeType: 'image/webp' } }))).toBe('image')
expect(contentPieceMediaKind(item({ media: { id: 7, mimeType: 'text/plain' } }))).toBe('text')
expect(contentPieceMediaKind(item({ media: { id: 7, mimeType: 'application/pdf' } }))).toBe(
'other',
)
// No MIME stored: the editorial type decides.
expect(contentPieceMediaKind(item({ media: { id: 7 } }))).toBe('video')
expect(contentPieceMediaKind(item({ type: 'card', media: { id: 7 } }))).toBe('image')
expect(contentPieceMediaKind(item({ media: null, sourceUrl: 'https://youtu.be/X' }))).toBeNull()
})
it('maps the frame of the type, leaving the wide kinds without a class (S45)', () => {
expect(contentPieceAspectClass('video')).toBe('aspect-[9/16]')
expect(contentPieceAspectClass('image')).toBe('aspect-[4/5]')
// The S38 model art is not a media kind: its host reads the table directly.
expect(CONTENT_PIECE_ASPECT_CLASS.model).toBe('aspect-[3/4]')
// Audio, link, text and other have no framing to preserve.
for (const kind of ['audio', 'text', 'other', null] as const) {
expect(contentPieceAspectClass(kind)).toBeUndefined()
}
})
it('buckets the board by type: vertical, wide or compact (S45)', () => {
expect(contentPieceBoardBucket('video')).toBe('vertical')
expect(contentPieceBoardBucket('audio')).toBe('wide')
expect(contentPieceBoardBucket(null)).toBe('wide')
for (const kind of ['image', 'text', 'other'] as const) {
expect(contentPieceBoardBucket(kind)).toBe('compact')
}
})
it('leads the metadata line with who appears in the piece (S37)', () => {
expect(item({ leaderNames: ['Maria Silva'] }).metaLabel).toBe(
'Maria Silva · Economia e Trabalho · Salvador',
)
// A figure takes the slot when there is no leader name.
expect(item({ publicFigures: ['Dep. Estadual Exemplo'] }).metaLabel).toBe(
'Dep. Estadual Exemplo · Economia e Trabalho · Salvador',
)
// Nobody marked: the previous line stays byte-identical.
expect(item().metaLabel).toBe('Economia e Trabalho · Salvador')
})
it('unions leaders and figures as people options, deduped by display name (S37)', () => {
expect(
contentPiecePublicPeople({
leaderNames: ['Maria Silva'],
publicFigures: ['Maria Silva', 'Dep. Estadual Exemplo'],
}),
).toEqual([
{ slug: 'maria-silva', name: 'Maria Silva' },
{ slug: 'dep-estadual-exemplo', name: 'Dep. Estadual Exemplo' },
])
expect(contentPiecePublicPeople({ leaderNames: [], publicFigures: [' '] })).toEqual([])
})
it('fails closed without a slug or without file and link', () => {
expect(toContentPiecePublicItem(source({ slug: null }))).toBeNull()
expect(toContentPiecePublicItem(source({ status: 'rascunho' }))).toBeNull()
expect(toContentPiecePublicItem(source({ media: null }))).toBeNull()
expect(toContentPiecePublicItem(source({ media: null, sourceUrl: null }))).toBeNull()
expect(
toContentPiecePublicItem(source({ media: null, sourceUrl: 'https://youtu.be/X' }))?.isLink,
).toBe(true)
})
it('keeps the text excerpt short and the download name legible', () => {
expect(contentPieceTextExcerpt('\n “Saúde é direito.” \nSegunda linha')).toBe(
'“Saúde é direito.”',
)
expect(contentPieceTextExcerpt('')).toBeNull()
expect(contentPieceTextExcerpt('a'.repeat(200))?.endsWith('…')).toBe(true)
expect(contentPieceDownloadFilename('card-feira', 'Card Final.PNG')).toBe(
'jorge-solla-1313-card-feira.png',
)
expect(contentPieceDownloadFilename('sem-extensao', null)).toBe(
'jorge-solla-1313-sem-extensao.bin',
)
expect(contentPiecePublicPath('x')).toBe('/conteudos/x')
expect(contentPieceMediaPath('x')).toBe('/conteudos/x/midia')
expect(contentPieceFramePath('x')).toBe('/conteudos/x/frame')
})
})