Conservar progresso editorial entre renovações MFA e tornar a retoma visível
tests / ci (push) Canceled after 0s
tests / ci (push) Canceled after 0s
This commit is contained in:
@@ -97,7 +97,7 @@ final class FundingProgrammeReferences
|
||||
'id' => $row?->id, 'published' => $row?->published_version_id !== null];
|
||||
}
|
||||
|
||||
return ['dataset_hash' => $package['sha256'], 'items' => $items];
|
||||
return ['dataset_hash' => $package['sha256'], 'editor_id' => $actor->id, 'items' => $items];
|
||||
}
|
||||
|
||||
/** @return array{id:string,created:bool} */
|
||||
|
||||
@@ -1,3 +1,15 @@
|
||||
## 10/11/2026 — retoma após MFA, sem repetir fichas conferidas
|
||||
|
||||
Verificação activa posterior:90rascunhos/zero publicações; continuação GETMomentum403 às21:49:39 ePOSTpublicar403 às21:49:54 (+02). A sessão voltou a expirar. A publicação comunicada não chegou a ser executada pelo servidor. Nenhum rascunho foi removido.
|
||||
|
||||
Correcção delimitada emQA: a mesma aba conserva selecção, recibos/versionamento/hash e fundamento, separados por conta e conjunto de fontes; após reautenticação salta fichas já conferidas. As confirmações de revisão/publicação são desmarcadas na nova entrada, sem publicação automática. Uma publicação de resposta incerta conserva o mesmo corpo para recuperação idempotente. Novo comando de renovação utiliza logout/login/MFA nativos; limite900s e guardas mantidos. «Seleccionar todas as instituições(270)», contagem de progresso e controlos antes da lista recolhida tornam o estado visível. Sem migração, dados novos ou alterações de permissões. Alteração ainda não instalada nesta anotação; revisão activaef24cbdc.
|
||||
|
||||
Prova dirigida do componente efectivo: remonta a página como após autenticação, recupera selecção/recibos, não repete preparação, não publica automaticamente, recupera confirmação incerta e isola outra conta/fontes. Prova API editorial anterior1caso/27asserções reexecutada apenas pela propriedadeeditor_id acrescentada; provas270fichas e do dossier reutilizadas. Tipos/análise/compilação da entrega são registados pelo executor existente.
|
||||
|
||||
## 10/11/2026 — confirmação da preparação parcial e expiração do MFA
|
||||
|
||||
Após execução no editor, a homologação contém **32rascunhos e zero fichas publicadas**. A preparação gravou efectivamente e avançou por diferentes instituições. Os registos HTTP mostram preparação200 atéMyBucks às21:31:42, seguida deGETxtenda403 às21:31:43 ePOSTpublicar403 às21:31:59 (Africa/Johannesburg). A entrada no editor fora confirmada às21:16:43; corresponde ao limite existente de900segundos do MFA em `PortalProfileAccess::mfa`. Não é perda da instalação nem repetição de dados. Reautenticar no portal com MFA e retomar a preparação idempotente conserva os32rascunhos, sem substituir edições; publicação continua a exigir a confirmação separada. Nenhuma guarda, sessão ou permissão foi alterada. Revisão instalada `ef24cbdc`, ACT12 ainda sem conteúdo público confirmado. Prova de leitura: `outputs/funding-directory-20261110/institutions/publication-observation.json`.
|
||||
|
||||
## 10/11/2026 — instalação confirmada do directório financeiro
|
||||
|
||||
Revisão `ef24cbdc9218e2027f7d1a39968cc60869a35410` instalada na homologação às19:15UTC, pelo executor Git existente.33ficheiros conferidos por hash com o commit; configuração preservada, zero migrações/importações automáticas. Backup/restauro confirmado de190tabelas e369ficheiros. Entradas `/fundos?kind=institution`, `/concursos`, `/login`, `/up` e revisão HTTP200. Pacote anterior preservado; nova entrega `outputs/entrega-instituicoes-editorial-20261110.zip` SHA256 `168d7ffb6d054533674f3f07799959b46bc12641c045accfc1766da5cead9077`.
|
||||
|
||||
@@ -1,3 +1,15 @@
|
||||
## 10/11/2026 — retoma após MFA, sem repetir fichas conferidas
|
||||
|
||||
Verificação activa posterior:90rascunhos/zero publicações; continuação GETMomentum403 às21:49:39 ePOSTpublicar403 às21:49:54 (+02). A sessão voltou a expirar. A publicação comunicada não chegou a ser executada pelo servidor. Nenhum rascunho foi removido.
|
||||
|
||||
Correcção delimitada emQA: a mesma aba conserva selecção, recibos/versionamento/hash e fundamento, separados por conta e conjunto de fontes; após reautenticação salta fichas já conferidas. As confirmações de revisão/publicação são desmarcadas na nova entrada, sem publicação automática. Uma publicação de resposta incerta conserva o mesmo corpo para recuperação idempotente. Novo comando de renovação utiliza logout/login/MFA nativos; limite900s e guardas mantidos. «Seleccionar todas as instituições(270)», contagem de progresso e controlos antes da lista recolhida tornam o estado visível. Sem migração, dados novos ou alterações de permissões. Alteração ainda não instalada nesta anotação; revisão activaef24cbdc.
|
||||
|
||||
Prova dirigida do componente efectivo: remonta a página como após autenticação, recupera selecção/recibos, não repete preparação, não publica automaticamente, recupera confirmação incerta e isola outra conta/fontes. Prova API editorial anterior1caso/27asserções reexecutada apenas pela propriedadeeditor_id acrescentada; provas270fichas e do dossier reutilizadas. Tipos/análise/compilação da entrega são registados pelo executor existente.
|
||||
|
||||
## 10/11/2026 — confirmação da preparação parcial e expiração do MFA
|
||||
|
||||
Após execução no editor, a homologação contém **32rascunhos e zero fichas publicadas**. A preparação gravou efectivamente e avançou por diferentes instituições. Os registos HTTP mostram preparação200 atéMyBucks às21:31:42, seguida deGETxtenda403 às21:31:43 ePOSTpublicar403 às21:31:59 (Africa/Johannesburg). A entrada no editor fora confirmada às21:16:43; corresponde ao limite existente de900segundos do MFA em `PortalProfileAccess::mfa`. Não é perda da instalação nem repetição de dados. Reautenticar no portal com MFA e retomar a preparação idempotente conserva os32rascunhos, sem substituir edições; publicação continua a exigir a confirmação separada. Nenhuma guarda, sessão ou permissão foi alterada. Revisão instalada `ef24cbdc`, ACT12 ainda sem conteúdo público confirmado. Prova de leitura: `outputs/funding-directory-20261110/institutions/publication-observation.json`.
|
||||
|
||||
## 10/11/2026 — instalação confirmada do directório financeiro
|
||||
|
||||
Revisão `ef24cbdc9218e2027f7d1a39968cc60869a35410` instalada na homologação às19:15UTC, pelo executor Git existente.33ficheiros conferidos por hash com o commit; configuração preservada, zero migrações/importações automáticas. Backup/restauro confirmado de190tabelas e369ficheiros. Entradas `/fundos?kind=institution`, `/concursos`, `/login`, `/up` e revisão HTTP200. Pacote anterior preservado; nova entrega `outputs/entrega-instituicoes-editorial-20261110.zip` SHA256 `168d7ffb6d054533674f3f07799959b46bc12641c045accfc1766da5cead9077`.
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,3 +1,15 @@
|
||||
## 10/11/2026 — retoma após MFA, sem repetir fichas conferidas
|
||||
|
||||
Verificação activa posterior:90rascunhos/zero publicações; continuação GETMomentum403 às21:49:39 ePOSTpublicar403 às21:49:54 (+02). A sessão voltou a expirar. A publicação comunicada não chegou a ser executada pelo servidor. Nenhum rascunho foi removido.
|
||||
|
||||
Correcção delimitada emQA: a mesma aba conserva selecção, recibos/versionamento/hash e fundamento, separados por conta e conjunto de fontes; após reautenticação salta fichas já conferidas. As confirmações de revisão/publicação são desmarcadas na nova entrada, sem publicação automática. Uma publicação de resposta incerta conserva o mesmo corpo para recuperação idempotente. Novo comando de renovação utiliza logout/login/MFA nativos; limite900s e guardas mantidos. «Seleccionar todas as instituições(270)», contagem de progresso e controlos antes da lista recolhida tornam o estado visível. Sem migração, dados novos ou alterações de permissões. Alteração ainda não instalada nesta anotação; revisão activaef24cbdc.
|
||||
|
||||
Prova dirigida do componente efectivo: remonta a página como após autenticação, recupera selecção/recibos, não repete preparação, não publica automaticamente, recupera confirmação incerta e isola outra conta/fontes. Prova API editorial anterior1caso/27asserções reexecutada apenas pela propriedadeeditor_id acrescentada; provas270fichas e do dossier reutilizadas. Tipos/análise/compilação da entrega são registados pelo executor existente.
|
||||
|
||||
## 10/11/2026 — confirmação da preparação parcial e expiração do MFA
|
||||
|
||||
Após execução no editor, a homologação contém **32rascunhos e zero fichas publicadas**. A preparação gravou efectivamente e avançou por diferentes instituições. Os registos HTTP mostram preparação200 atéMyBucks às21:31:42, seguida deGETxtenda403 às21:31:43 ePOSTpublicar403 às21:31:59 (Africa/Johannesburg). A entrada no editor fora confirmada às21:16:43; corresponde ao limite existente de900segundos do MFA em `PortalProfileAccess::mfa`. Não é perda da instalação nem repetição de dados. Reautenticar no portal com MFA e retomar a preparação idempotente conserva os32rascunhos, sem substituir edições; publicação continua a exigir a confirmação separada. Nenhuma guarda, sessão ou permissão foi alterada. Revisão instalada `ef24cbdc`, ACT12 ainda sem conteúdo público confirmado. Prova de leitura: `outputs/funding-directory-20261110/institutions/publication-observation.json`.
|
||||
|
||||
## 10/11/2026 — instalação confirmada do directório financeiro
|
||||
|
||||
Revisão `ef24cbdc9218e2027f7d1a39968cc60869a35410` instalada na homologação às19:15UTC, pelo executor Git existente.33ficheiros conferidos por hash com o commit; configuração preservada, zero migrações/importações automáticas. Backup/restauro confirmado de190tabelas e369ficheiros. Entradas `/fundos?kind=institution`, `/concursos`, `/login`, `/up` e revisão HTTP200. Pacote anterior preservado; nova entrega `outputs/entrega-instituicoes-editorial-20261110.zip` SHA256 `168d7ffb6d054533674f3f07799959b46bc12641c045accfc1766da5cead9077`.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Link } from "@inertiajs/react";
|
||||
import { useRef, useState } from "react";
|
||||
import { Link, router } from "@inertiajs/react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
financialCategories,
|
||||
financialTypes,
|
||||
@@ -27,21 +27,82 @@ type Receipt = {
|
||||
published?: boolean;
|
||||
};
|
||||
|
||||
export default function Assisted({
|
||||
type Progress = {
|
||||
selected: string[];
|
||||
receipts: Record<string, Receipt>;
|
||||
reason: string;
|
||||
pending: Record<string, object>;
|
||||
};
|
||||
const emptyProgress = (): Progress => ({
|
||||
selected: [],
|
||||
receipts: {},
|
||||
reason: "",
|
||||
pending: {},
|
||||
});
|
||||
export function readProgress(key: string): Progress {
|
||||
try {
|
||||
const value = JSON.parse(sessionStorage.getItem(key) ?? "null");
|
||||
if (
|
||||
!value ||
|
||||
!Array.isArray(value.selected) ||
|
||||
!value.selected.every((c: unknown) => typeof c === "string") ||
|
||||
!value.receipts ||
|
||||
typeof value.receipts !== "object" ||
|
||||
typeof value.reason !== "string" ||
|
||||
!value.pending ||
|
||||
typeof value.pending !== "object"
|
||||
)
|
||||
return emptyProgress();
|
||||
return value;
|
||||
} catch {
|
||||
return emptyProgress();
|
||||
}
|
||||
}
|
||||
function saveProgress(key: string, value: Progress): void {
|
||||
try {
|
||||
sessionStorage.setItem(key, JSON.stringify(value));
|
||||
} catch {
|
||||
/* Native receipts remain authoritative if tab storage is unavailable. */
|
||||
}
|
||||
}
|
||||
|
||||
export function AssistedEditor({
|
||||
batch,
|
||||
}: {
|
||||
batch: { dataset_hash: string; items: Item[] };
|
||||
batch: { dataset_hash: string; editor_id: number; items: Item[] };
|
||||
}) {
|
||||
const [selected, setSelected] = useState<string[]>([]);
|
||||
const [receipts, setReceipts] = useState<Record<string, Receipt>>({});
|
||||
const storageKey = `investmoz-funding-progress:${batch.editor_id}:${batch.dataset_hash}`;
|
||||
const [restored] = useState(() => readProgress(storageKey));
|
||||
const [selected, setSelected] = useState<string[]>(
|
||||
restored.selected.filter((c) => batch.items.some((i) => i.code === c)),
|
||||
);
|
||||
const [receipts, setReceipts] = useState<Record<string, Receipt>>(
|
||||
restored.receipts,
|
||||
);
|
||||
const [reviewed, setReviewed] = useState(false);
|
||||
const [confirmed, setConfirmed] = useState(false);
|
||||
const [reason, setReason] = useState("");
|
||||
const [reason, setReason] = useState(restored.reason);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [message, setMessage] = useState("");
|
||||
const running = useRef(false);
|
||||
const stop = useRef(false);
|
||||
const pending = useRef<Record<string, object>>({});
|
||||
const pending = useRef<Record<string, object>>(restored.pending);
|
||||
const [needsMfa, setNeedsMfa] = useState(false);
|
||||
useEffect(() => {
|
||||
saveProgress(storageKey, {
|
||||
selected,
|
||||
receipts,
|
||||
reason,
|
||||
pending: pending.current,
|
||||
});
|
||||
}, [storageKey, selected, receipts, reason]);
|
||||
const ready = selected.filter(
|
||||
(c) =>
|
||||
receipts[c]?.matches_reference &&
|
||||
receipts[c]?.publication &&
|
||||
!receipts[c]?.published,
|
||||
).length;
|
||||
const published = selected.filter((c) => receipts[c]?.published).length;
|
||||
async function run(publish: boolean) {
|
||||
if (
|
||||
running.current ||
|
||||
@@ -57,6 +118,7 @@ export default function Assisted({
|
||||
try {
|
||||
for (const code of selected) {
|
||||
if (stop.current) break;
|
||||
if (!publish && receipts[code]) continue;
|
||||
setMessage(
|
||||
`${publish ? "Publicação" : "Preparação"}: ${batch.items.find((i) => i.code === code)?.title}`,
|
||||
);
|
||||
@@ -74,6 +136,12 @@ export default function Assisted({
|
||||
confirm: true,
|
||||
};
|
||||
pending.current[code] = body;
|
||||
saveProgress(storageKey, {
|
||||
selected,
|
||||
receipts,
|
||||
reason,
|
||||
pending: pending.current,
|
||||
});
|
||||
await profileJson(
|
||||
`/management/fundos/${receipt.id}/publicar`,
|
||||
body,
|
||||
@@ -108,6 +176,14 @@ export default function Assisted({
|
||||
: "Preparação concluída. Confira os resultados antes de confirmar a publicação.",
|
||||
);
|
||||
} catch (error) {
|
||||
setNeedsMfa(
|
||||
!!error &&
|
||||
typeof error === "object" &&
|
||||
"status" in error &&
|
||||
(error.status === 401 ||
|
||||
error.status === 403 ||
|
||||
error.status === 419),
|
||||
);
|
||||
setMessage(
|
||||
`${error instanceof Error ? error.message : "Operação interrompida."} Os resultados anteriores foram conservados. Se a sessão ou MFA expirou, autentique-se no portal e retome. A publicação incerta pode ser repetida com a mesma confirmação.`,
|
||||
);
|
||||
@@ -145,71 +221,94 @@ export default function Assisted({
|
||||
setConfirmed(false);
|
||||
}}
|
||||
>
|
||||
Seleccionar instituições do documento
|
||||
Seleccionar todas as instituições (270)
|
||||
</button>
|
||||
<p>{selected.length} seleccionadas</p>
|
||||
{batch.items.map((item) => (
|
||||
<details key={item.code} className="border-b py-2">
|
||||
<summary>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selected.includes(item.code)}
|
||||
onChange={(e) => {
|
||||
setSelected((old) =>
|
||||
e.target.checked
|
||||
? [...old, item.code]
|
||||
: old.filter(
|
||||
(c) => c !== item.code,
|
||||
),
|
||||
);
|
||||
setConfirmed(false);
|
||||
}}
|
||||
/>{" "}
|
||||
{item.title}
|
||||
</label>{" "}
|
||||
—{" "}
|
||||
{receipts[item.code]?.published
|
||||
? "Publicação confirmada"
|
||||
: receipts[item.code]
|
||||
? receipts[item.code].matches_reference
|
||||
? "Conferida, aguarda publicação"
|
||||
: "Edições existentes: rever individualmente"
|
||||
: item.published
|
||||
? "Já tem publicação"
|
||||
: "Por preparar"}
|
||||
</summary>
|
||||
<p>{item.summary}</p>
|
||||
<p>
|
||||
{item.categories.map(c => financialCategories[c] ?? c).join(", ")} —{" "}
|
||||
{item.types.map(t => financialTypes[t] ?? t).join(", ")} — {item.status}
|
||||
</p>
|
||||
<p>
|
||||
{item.source_document}{" "}
|
||||
<code className="break-all">
|
||||
{item.source_sha256}
|
||||
</code>
|
||||
</p>
|
||||
<p>
|
||||
{item.locations.length} localizações;{" "}
|
||||
{item.contacts.length} contactos.
|
||||
</p>
|
||||
<Link
|
||||
className="underline"
|
||||
href={`/management/fundos/referencias/${item.code}`}
|
||||
>
|
||||
Conferir fonte e dados completos
|
||||
</Link>
|
||||
{receipts[item.code] && (
|
||||
<p role="status">
|
||||
{selected.length} seleccionadas ·{" "}
|
||||
{Object.keys(receipts).length} preparadas nesta retoma ·{" "}
|
||||
{ready} prontas para publicar · {published} publicadas
|
||||
</p>
|
||||
<p>
|
||||
O progresso fica guardado nesta aba, separado por conta e
|
||||
fontes. Depois de renovar o MFA, retome os passos sem voltar
|
||||
a preparar as fichas conferidas. As confirmações devem ser
|
||||
marcadas novamente; nenhuma publicação é automática.
|
||||
</p>
|
||||
<details>
|
||||
<summary>
|
||||
Conferir a lista e alterar a selecção (
|
||||
{batch.items.length} referências)
|
||||
</summary>
|
||||
{batch.items.map((item) => (
|
||||
<details key={item.code} className="border-b py-2">
|
||||
<summary>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selected.includes(item.code)}
|
||||
onChange={(e) => {
|
||||
setSelected((old) =>
|
||||
e.target.checked
|
||||
? [...old, item.code]
|
||||
: old.filter(
|
||||
(c) =>
|
||||
c !== item.code,
|
||||
),
|
||||
);
|
||||
setConfirmed(false);
|
||||
}}
|
||||
/>{" "}
|
||||
{item.title}
|
||||
</label>{" "}
|
||||
—{" "}
|
||||
{receipts[item.code]?.published
|
||||
? "Publicação confirmada"
|
||||
: receipts[item.code]
|
||||
? receipts[item.code].matches_reference
|
||||
? "Conferida, aguarda publicação"
|
||||
: "Edições existentes: rever individualmente"
|
||||
: item.published
|
||||
? "Já tem publicação"
|
||||
: "Por preparar"}
|
||||
</summary>
|
||||
<p>{item.summary}</p>
|
||||
<p>
|
||||
{item.categories
|
||||
.map((c) => financialCategories[c] ?? c)
|
||||
.join(", ")}{" "}
|
||||
—{" "}
|
||||
{item.types
|
||||
.map((t) => financialTypes[t] ?? t)
|
||||
.join(", ")}{" "}
|
||||
— {item.status}
|
||||
</p>
|
||||
<p>
|
||||
{item.source_document}{" "}
|
||||
<code className="break-all">
|
||||
{item.source_sha256}
|
||||
</code>
|
||||
</p>
|
||||
<p>
|
||||
{item.locations.length} localizações;{" "}
|
||||
{item.contacts.length} contactos.
|
||||
</p>
|
||||
<Link
|
||||
className="ml-4 underline"
|
||||
href={`/management/fundos/${receipts[item.code].id}`}
|
||||
className="underline"
|
||||
href={`/management/fundos/referencias/${item.code}`}
|
||||
>
|
||||
Alterar ficha existente
|
||||
Conferir fonte e dados completos
|
||||
</Link>
|
||||
)}
|
||||
</details>
|
||||
))}
|
||||
{receipts[item.code] && (
|
||||
<Link
|
||||
className="ml-4 underline"
|
||||
href={`/management/fundos/${receipts[item.code].id}`}
|
||||
>
|
||||
Alterar ficha existente
|
||||
</Link>
|
||||
)}
|
||||
</details>
|
||||
))}
|
||||
</details>
|
||||
<label className="block py-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -245,6 +344,11 @@ export default function Assisted({
|
||||
Confirmo a publicação pública das versões conferidas,
|
||||
preservando os dados e as incertezas apresentados.
|
||||
</label>
|
||||
<p>
|
||||
Para publicar: termine a preparação, marque a conferência,
|
||||
preencha o fundamento e confirme a publicação. Fichas com
|
||||
edições existentes exigem revisão individual.
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
className={button}
|
||||
@@ -263,6 +367,33 @@ export default function Assisted({
|
||||
2. Publicar conferidas
|
||||
</button>
|
||||
</fieldset>
|
||||
{needsMfa && (
|
||||
<div role="alert" className={card}>
|
||||
<p>
|
||||
A sessão ou confirmação MFA precisa de renovação. O
|
||||
progresso desta aba foi conservado.
|
||||
</p>
|
||||
<button
|
||||
className={button}
|
||||
type="button"
|
||||
disabled={busy}
|
||||
onClick={() =>
|
||||
router.post(
|
||||
"/logout",
|
||||
{},
|
||||
{
|
||||
onSuccess: () =>
|
||||
router.visit(
|
||||
"/management/fundos/referencias-assistidas",
|
||||
),
|
||||
},
|
||||
)
|
||||
}
|
||||
>
|
||||
Renovar entrada com MFA e retomar
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{busy && (
|
||||
<button
|
||||
type="button"
|
||||
@@ -283,3 +414,16 @@ export default function Assisted({
|
||||
</Frame>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Assisted({
|
||||
batch,
|
||||
}: {
|
||||
batch: { dataset_hash: string; editor_id: number; items: Item[] };
|
||||
}) {
|
||||
return (
|
||||
<AssistedEditor
|
||||
key={`${batch.editor_id}:${batch.dataset_hash}`}
|
||||
batch={batch}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -124,7 +124,13 @@ const text = (node) =>
|
||||
? text(node.props?.children)
|
||||
: String(node ?? "");
|
||||
|
||||
test("assisted editorial preparation never publishes until a separate confirmation and retries the same publication", async () => {
|
||||
test("assisted editor resumes after renewed authentication without preparing completed fiches or automatically publishing", async () => {
|
||||
const previousStorage = globalThis.sessionStorage;
|
||||
const tab = new Map();
|
||||
globalThis.sessionStorage = {
|
||||
getItem: (key) => tab.get(key) ?? null,
|
||||
setItem: (key, value) => tab.set(key, value),
|
||||
};
|
||||
const states = [],
|
||||
refs = [],
|
||||
calls = [];
|
||||
@@ -133,9 +139,13 @@ test("assisted editorial preparation never publishes until a separate confirmati
|
||||
lost = true;
|
||||
const hooks = {
|
||||
...React,
|
||||
useEffect(fn) {
|
||||
fn();
|
||||
},
|
||||
useState(initial) {
|
||||
const i = si++;
|
||||
if (!(i in states)) states[i] = initial;
|
||||
if (!(i in states))
|
||||
states[i] = typeof initial === "function" ? initial() : initial;
|
||||
return [
|
||||
states[i],
|
||||
(v) => {
|
||||
@@ -175,10 +185,11 @@ test("assisted editorial preparation never publishes until a separate confirmati
|
||||
return { package_sha256: "qa-dataset", hash: "qa-source" };
|
||||
},
|
||||
},
|
||||
}).default;
|
||||
}).AssistedEditor;
|
||||
const props = {
|
||||
batch: {
|
||||
dataset_hash: "qa-dataset",
|
||||
editor_id: 42,
|
||||
items: [
|
||||
{
|
||||
code: "qa",
|
||||
@@ -214,6 +225,23 @@ test("assisted editorial preparation never publishes until a separate confirmati
|
||||
assert.equal(calls.length, 2);
|
||||
assert.ok(calls.every((c) => !c.url.endsWith("/publicar")));
|
||||
tree = render();
|
||||
states.length = 0;
|
||||
refs.length = 0; // Full page remount after login/MFA in the same tab.
|
||||
tree = render();
|
||||
assert.match(text(tree), /1 seleccionadas/);
|
||||
assert.match(text(tree), /1 preparadas nesta retoma/);
|
||||
boxes = flatten(tree).filter((n) => n.type === "input");
|
||||
assert.equal(boxes[1].props.checked, false);
|
||||
boxes[1].props.onChange({ target: { checked: true } });
|
||||
tree = render();
|
||||
click(tree, "1.");
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
assert.equal(
|
||||
calls.length,
|
||||
2,
|
||||
"A completed fiche must not be prepared again",
|
||||
);
|
||||
tree = render();
|
||||
assert.equal(
|
||||
flatten(tree).find(
|
||||
(n) => n.type === "button" && text(n).startsWith("2."),
|
||||
@@ -246,6 +274,25 @@ test("assisted editorial preparation never publishes until a separate confirmati
|
||||
assert.deepEqual(calls[3], calls[2]);
|
||||
assert.equal(calls[2].body.confirm, true);
|
||||
assert.match(text(render()), /Publicação confirmada/);
|
||||
props.batch.editor_id = 43;
|
||||
states.length = 0;
|
||||
refs.length = 0;
|
||||
assert.match(
|
||||
text(render()),
|
||||
/0 seleccionadas/,
|
||||
"Another account must not inherit progress",
|
||||
);
|
||||
props.batch.editor_id = 42;
|
||||
props.batch.dataset_hash = "changed-source";
|
||||
states.length = 0;
|
||||
refs.length = 0;
|
||||
assert.match(
|
||||
text(render()),
|
||||
/0 seleccionadas/,
|
||||
"Changed sources must not inherit receipts",
|
||||
);
|
||||
if (previousStorage === undefined) delete globalThis.sessionStorage;
|
||||
else globalThis.sessionStorage = previousStorage;
|
||||
});
|
||||
test("actual dossier continuation freezes a lost response retry and retains dossier/opportunity and selected product", async () => {
|
||||
const states = [],
|
||||
@@ -255,9 +302,11 @@ test("actual dossier continuation freezes a lost response retry and retains doss
|
||||
const calls = [];
|
||||
const hooks = {
|
||||
...React,
|
||||
useEffect() {},
|
||||
useState(initial) {
|
||||
const i = stateIndex++;
|
||||
if (!(i in states)) states[i] = initial;
|
||||
if (!(i in states))
|
||||
states[i] = typeof initial === "function" ? initial() : initial;
|
||||
return [states[i], (value) => (states[i] = value)];
|
||||
},
|
||||
useRef(initial) {
|
||||
|
||||
Reference in New Issue
Block a user