@remnic/core 9.26.0 → 9.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-admin-ops-surface.d.ts +13 -10
- package/dist/access-admin-ops-surface.js +43 -38
- package/dist/access-audit.js +2 -2
- package/dist/access-authorization-probe.d.ts +13 -10
- package/dist/access-authorization-probe.js +44 -39
- package/dist/access-boundary.d.ts +13 -10
- package/dist/access-boundary.js +43 -38
- package/dist/access-cli.js +100 -94
- package/dist/access-cli.js.map +1 -1
- package/dist/access-health-types.d.ts +10 -1
- package/dist/access-http-lcm-compaction.d.ts +13 -10
- package/dist/access-http.d.ts +13 -10
- package/dist/access-http.js +53 -48
- package/dist/access-identity-continuity-surface.d.ts +9 -8
- package/dist/access-identity-continuity-surface.js +43 -38
- package/dist/access-lcm-surface.d.ts +13 -10
- package/dist/access-lcm-surface.js +43 -38
- package/dist/access-mcp.d.ts +13 -10
- package/dist/access-mcp.js +48 -43
- package/dist/access-memory-search-fanout.d.ts +2 -2
- package/dist/{access-namespace-preflight-wRSZEO5V.d.ts → access-namespace-preflight-BiQ_-iUJ.d.ts} +1 -1
- package/dist/access-namespace-preflight.d.ts +3 -3
- package/dist/access-namespace-preflight.js +43 -38
- package/dist/access-observe-write-surface.d.ts +13 -10
- package/dist/access-observe-write-surface.js +43 -38
- package/dist/access-operations-batch.js +45 -40
- package/dist/access-operations.d.ts +16 -13
- package/dist/access-operations.js +47 -42
- package/dist/access-recall-concurrency.d.ts +13 -10
- package/dist/access-recall-concurrency.js +43 -38
- package/dist/access-recall-response.d.ts +13 -10
- package/dist/access-recall-response.js +43 -38
- package/dist/access-recall-surface.d.ts +13 -10
- package/dist/access-recall-surface.js +43 -38
- package/dist/access-schema.d.ts +80 -80
- package/dist/access-schema.js +4 -4
- package/dist/{access-service-DL8FJnTj.d.ts → access-service-CXkIgcGh.d.ts} +12 -8
- package/dist/access-service.d.ts +13 -10
- package/dist/access-service.js +43 -38
- package/dist/access-surface-catalog.d.ts +13 -10
- package/dist/access-wearables-meetings-surface.d.ts +4 -3
- package/dist/action-confidence.d.ts +1 -1
- package/dist/active-memory-bridge.d.ts +1 -1
- package/dist/active-recall.d.ts +1 -1
- package/dist/active-recall.js +4 -2
- package/dist/active-recall.js.map +1 -1
- package/dist/adapters/index.js +7 -7
- package/dist/adapters/registry.js +3 -3
- package/dist/behavior-learner.d.ts +1 -1
- package/dist/behavior-signals.d.ts +1 -1
- package/dist/bootstrap.d.ts +9 -8
- package/dist/briefing.d.ts +3 -2
- package/dist/briefing.js +12 -10
- package/dist/buffer-surprise-report.d.ts +1 -1
- package/dist/buffer.d.ts +3 -2
- package/dist/bulk-import/index.d.ts +3 -3
- package/dist/calibration.d.ts +1 -1
- package/dist/capabilities.d.ts +1 -1
- package/dist/{capsule-crypto-YO5QJ6L3.js → capsule-crypto-7FJQINUR.js} +2 -2
- package/dist/{catalog-BLLxZ5L5.d.ts → catalog-Bouj8Gfg.d.ts} +1 -1
- package/dist/causal-behavior.d.ts +1 -1
- package/dist/causal-consolidation.d.ts +1 -1
- package/dist/causal-consolidation.js +13 -11
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/causal-trajectory-graph.d.ts +1 -1
- package/dist/{chunk-7RZJWZ46.js → chunk-227UFSEQ.js} +5 -5
- package/dist/{chunk-VGEH3STZ.js → chunk-2BEJ4XCF.js} +2 -2
- package/dist/{chunk-LIIQPDFN.js → chunk-2FYNLDKJ.js} +24 -3
- package/dist/chunk-2FYNLDKJ.js.map +1 -0
- package/dist/{chunk-BXLOS5AJ.js → chunk-2NLLXCJG.js} +2 -2
- package/dist/{chunk-6NNNURLK.js → chunk-2PHX3I5C.js} +8 -8
- package/dist/{chunk-YN4AUGNF.js → chunk-2TQ2UFKH.js} +65 -60
- package/dist/chunk-2TQ2UFKH.js.map +1 -0
- package/dist/{chunk-XAAIUXJO.js → chunk-3BSJBIZ3.js} +2 -2
- package/dist/{chunk-JCVQV6QX.js → chunk-3UOSVTFR.js} +11 -11
- package/dist/{chunk-AJ7QCSIS.js → chunk-7CPOZH6Y.js} +2 -2
- package/dist/{chunk-EXM2Q555.js → chunk-AHFZXUZM.js} +7 -5
- package/dist/chunk-AHFZXUZM.js.map +1 -0
- package/dist/{chunk-M2KGXXTL.js → chunk-ALTP3MFS.js} +2 -2
- package/dist/{chunk-77CNV7DY.js → chunk-BLAKS3CP.js} +4 -4
- package/dist/{chunk-FE5FKTCI.js → chunk-BR4UFMLO.js} +2 -2
- package/dist/chunk-CVILPI77.js +432 -0
- package/dist/chunk-CVILPI77.js.map +1 -0
- package/dist/{chunk-H2PBM562.js → chunk-DKQM5Q6Q.js} +2 -2
- package/dist/{chunk-CSXHRAJJ.js → chunk-EG64LXKL.js} +3 -3
- package/dist/{chunk-PTN6UAUA.js → chunk-ES36HTSC.js} +4 -4
- package/dist/{chunk-WMDRLTNA.js → chunk-EZWWCN7I.js} +2 -2
- package/dist/{chunk-XKGPIWGI.js → chunk-FBZTWUK5.js} +5 -5
- package/dist/{chunk-GJJBPI6I.js → chunk-FOCEI3TN.js} +6 -3
- package/dist/chunk-FOCEI3TN.js.map +1 -0
- package/dist/{chunk-Y6E37WRG.js → chunk-GJPCP5CA.js} +2 -2
- package/dist/{chunk-IHMXPG4P.js → chunk-GQSMYXKL.js} +6 -4
- package/dist/{chunk-IHMXPG4P.js.map → chunk-GQSMYXKL.js.map} +1 -1
- package/dist/{chunk-TGQ2NTWH.js → chunk-IJ6WHMFB.js} +16 -2
- package/dist/{chunk-TGQ2NTWH.js.map → chunk-IJ6WHMFB.js.map} +1 -1
- package/dist/{chunk-VDX2J7OX.js → chunk-IPLYGWQF.js} +6 -6
- package/dist/{chunk-XNUBF6RF.js → chunk-IYT632D5.js} +3 -3
- package/dist/{chunk-2QSZNTDO.js → chunk-JBPKEARU.js} +7 -7
- package/dist/{chunk-67DSDRUB.js → chunk-JIF3VRKQ.js} +8 -8
- package/dist/{chunk-V25OWXAC.js → chunk-JQ7T5VDC.js} +1 -1
- package/dist/chunk-JQ7T5VDC.js.map +1 -0
- package/dist/{chunk-MIFF37NN.js → chunk-KKBTBUVT.js} +2 -2
- package/dist/{chunk-ZHP4SUPB.js → chunk-LIHC3T43.js} +2 -2
- package/dist/{chunk-PX7VDW7R.js → chunk-MJPV4LBH.js} +79 -79
- package/dist/{chunk-PX7VDW7R.js.map → chunk-MJPV4LBH.js.map} +1 -1
- package/dist/{chunk-UGH6VPPO.js → chunk-MWTMTW2T.js} +2 -2
- package/dist/{chunk-F6LSNPPB.js → chunk-MXJ3VP6S.js} +6 -6
- package/dist/{chunk-KPWLZBXZ.js → chunk-NBNDVNJ2.js} +186 -25
- package/dist/chunk-NBNDVNJ2.js.map +1 -0
- package/dist/{chunk-M5EPMNS4.js → chunk-P2CLYN6H.js} +2 -2
- package/dist/{chunk-7XXCIO6X.js → chunk-PXQM2HOG.js} +13 -6
- package/dist/{chunk-7XXCIO6X.js.map → chunk-PXQM2HOG.js.map} +1 -1
- package/dist/{chunk-UZK7VVPD.js → chunk-Q5VJUNXA.js} +1858 -272
- package/dist/chunk-Q5VJUNXA.js.map +1 -0
- package/dist/{chunk-KGTD2KZJ.js → chunk-Q6YJW6KE.js} +2 -2
- package/dist/{chunk-X3SQYAPP.js → chunk-R372P2EH.js} +2 -2
- package/dist/{chunk-HBVOJNV4.js → chunk-RKXZ7P4V.js} +3 -3
- package/dist/chunk-RMMVQQA6.js +70 -0
- package/dist/chunk-RMMVQQA6.js.map +1 -0
- package/dist/{chunk-E6EAB4J6.js → chunk-S24E4MBQ.js} +14 -14
- package/dist/chunk-S24E4MBQ.js.map +1 -0
- package/dist/{chunk-RYHWZ52A.js → chunk-S2BBISHU.js} +4 -4
- package/dist/{chunk-LLISELSY.js → chunk-TBMRQZDY.js} +2 -2
- package/dist/{chunk-FMLKOL2Z.js → chunk-TC3VNKGX.js} +4 -4
- package/dist/{chunk-7K5Q6COX.js → chunk-TVVEYCNW.js} +4 -4
- package/dist/{chunk-2XP3PWL3.js → chunk-UR5SIN7H.js} +53 -35
- package/dist/chunk-UR5SIN7H.js.map +1 -0
- package/dist/{chunk-NKDVXEFU.js → chunk-VFNI3RPT.js} +2 -2
- package/dist/chunk-VNA7W7QF.js +134 -0
- package/dist/chunk-VNA7W7QF.js.map +1 -0
- package/dist/{chunk-PQPSZICJ.js → chunk-VYYNBJXH.js} +5 -5
- package/dist/chunk-WNIUPHWV.js +152 -0
- package/dist/chunk-WNIUPHWV.js.map +1 -0
- package/dist/chunk-WRJVB3C2.js +118 -0
- package/dist/chunk-WRJVB3C2.js.map +1 -0
- package/dist/{chunk-SN3ZESWQ.js → chunk-WVVK2RFM.js} +6 -6
- package/dist/{chunk-DQEMWVMT.js → chunk-X7Y7WX73.js} +1 -1
- package/dist/{chunk-4KKV7W62.js → chunk-YOBNCD3Z.js} +6 -6
- package/dist/chunk-YXZG5TVN.js +40 -0
- package/dist/chunk-YXZG5TVN.js.map +1 -0
- package/dist/{chunk-4DJQYKMN.js → chunk-ZIKENA2C.js} +2 -2
- package/dist/chunk-ZIKENA2C.js.map +1 -0
- package/dist/{chunk-SK3EYPKC.js → chunk-ZN4UZWMT.js} +2 -2
- package/dist/{cli-BoouX6LX.d.ts → cli-DJbkaZBU.d.ts} +5 -5
- package/dist/cli.d.ts +14 -12
- package/dist/cli.js +79 -74
- package/dist/compounding/engine.d.ts +3 -2
- package/dist/compounding/engine.js +12 -10
- package/dist/compounding/preference-consolidator.d.ts +1 -1
- package/dist/compression-optimizer.d.ts +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +4 -2
- package/dist/connectors/codex-materialize-runner.d.ts +1 -1
- package/dist/connectors/codex-materialize-runner.js +12 -10
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +12 -10
- package/dist/consolidation-provenance-check.d.ts +3 -2
- package/dist/consolidation-undo.d.ts +3 -2
- package/dist/contradiction/index.d.ts +3 -2
- package/dist/conversation-index/backend.d.ts +1 -1
- package/dist/conversation-index/backend.js +2 -2
- package/dist/conversation-index/chunker.d.ts +1 -1
- package/dist/conversation-index/faiss-adapter.d.ts +1 -1
- package/dist/conversation-index/indexer.d.ts +1 -1
- package/dist/conversation-index/search.d.ts +1 -1
- package/dist/corpus-watermark.d.ts +15 -2
- package/dist/corpus-watermark.js +25 -21
- package/dist/day-summary.d.ts +1 -1
- package/dist/delinearize.d.ts +1 -1
- package/dist/direct-answer-wiring.d.ts +1 -1
- package/dist/direct-answer.d.ts +1 -1
- package/dist/embedding-fallback.d.ts +1 -1
- package/dist/enrichment/index.d.ts +1 -1
- package/dist/entity-id-normalization.d.ts +4 -0
- package/dist/entity-id-normalization.js +10 -0
- package/dist/entity-id-normalization.js.map +1 -0
- package/dist/entity-retrieval-boundaries.d.ts +3 -0
- package/dist/entity-retrieval-boundaries.js +8 -0
- package/dist/entity-retrieval-boundaries.js.map +1 -0
- package/dist/entity-retrieval.d.ts +3 -2
- package/dist/entity-retrieval.js +13 -10
- package/dist/entity-schema.d.ts +1 -1
- package/dist/entity-schema.js +1 -1
- package/dist/explicit-capture.d.ts +9 -8
- package/dist/extraction-error-classification.d.ts +1 -1
- package/dist/extraction-faithfulness.d.ts +1 -1
- package/dist/extraction-judge-telemetry.d.ts +1 -1
- package/dist/extraction-judge-training.d.ts +1 -1
- package/dist/extraction-judge.d.ts +1 -1
- package/dist/extraction-liveness.d.ts +1 -1
- package/dist/extraction.d.ts +1 -1
- package/dist/fallback-llm.d.ts +1 -1
- package/dist/{first-start-migration-LEXL3XYL.js → first-start-migration-BKNGOFED.js} +4 -4
- package/dist/graph-dashboard-diff.d.ts +1 -1
- package/dist/graph-dashboard-key.d.ts +1 -1
- package/dist/graph-dashboard-parser.d.ts +1 -1
- package/dist/graph-edge-reinforcement.d.ts +1 -1
- package/dist/graph-snapshot.d.ts +1 -1
- package/dist/graph.d.ts +1 -1
- package/dist/importance.d.ts +1 -1
- package/dist/importers/index.d.ts +1 -1
- package/dist/in-flight-reads.d.ts +1 -1
- package/dist/index.d.ts +409 -407
- package/dist/index.js +147 -141
- package/dist/intent.d.ts +1 -1
- package/dist/lcm/engine.d.ts +1 -1
- package/dist/lcm/engine.js +3 -3
- package/dist/lcm/index.d.ts +1 -1
- package/dist/lcm/index.js +9 -9
- package/dist/lcm/tools.d.ts +1 -1
- package/dist/lifecycle.d.ts +1 -1
- package/dist/live-connectors-runner.d.ts +1 -1
- package/dist/local-llm.d.ts +1 -1
- package/dist/local-model-endpoint.d.ts +1 -1
- package/dist/maintenance/memory-governance.d.ts +1 -1
- package/dist/maintenance/memory-governance.js +12 -10
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +3 -2
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +12 -10
- package/dist/maintenance/rebuild-memory-projection.d.ts +3 -2
- package/dist/maintenance/rebuild-memory-projection.js +13 -11
- package/dist/{maintenance-DD22oIx9.d.ts → maintenance-DveW6azo.d.ts} +3 -3
- package/dist/mcp-memory-inspector-app.d.ts +13 -10
- package/dist/memory-action-policy.d.ts +1 -1
- package/dist/memory-cache.d.ts +1 -1
- package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
- package/dist/memory-projection-mutations.d.ts +4 -0
- package/dist/memory-projection-mutations.js +15 -0
- package/dist/memory-projection-mutations.js.map +1 -0
- package/dist/memory-projection-store.d.ts +1 -1
- package/dist/memory-provenance.d.ts +1 -1
- package/dist/memory-worth-outcomes.d.ts +3 -2
- package/dist/models-json.d.ts +1 -1
- package/dist/namespaces/migrate.d.ts +4 -3
- package/dist/namespaces/migrate.js +22 -20
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/search.js +9 -9
- package/dist/namespaces/storage.d.ts +4 -3
- package/dist/namespaces/storage.js +12 -10
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/offline-sync-impression-drain.d.ts +1 -1
- package/dist/offline-sync-impression-drain.js +3 -3
- package/dist/operator-doctor-corpus.d.ts +22 -3
- package/dist/operator-doctor-corpus.js +32 -22
- package/dist/operator-doctor-replica.d.ts +52 -0
- package/dist/operator-doctor-replica.js +15 -0
- package/dist/operator-doctor-replica.js.map +1 -0
- package/dist/operator-toolkit.d.ts +7 -3
- package/dist/operator-toolkit.js +33 -27
- package/dist/orchestration/compression-guideline-coordinator.d.ts +3 -2
- package/dist/orchestration/maintenance.d.ts +5 -4
- package/dist/orchestration/maintenance.js +17 -15
- package/dist/{orchestrator-CFJ_TG6D.d.ts → orchestrator-Cvlgpe6I.d.ts} +7 -7
- package/dist/orchestrator.d.ts +9 -8
- package/dist/orchestrator.js +99 -93
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/{pipeline-Bq5zU6Bp.d.ts → pipeline-BveYVaz7.d.ts} +1 -1
- package/dist/policy-runtime.d.ts +1 -1
- package/dist/proactive-contention.d.ts +1 -1
- package/dist/provenance.d.ts +1 -1
- package/dist/{qmd-3ILJkC70.d.ts → qmd-CLWDsbYR.d.ts} +1 -1
- package/dist/qmd-preflight.d.ts +2 -2
- package/dist/qmd-recall-cache.d.ts +1 -1
- package/dist/qmd.d.ts +2 -2
- package/dist/recall-concurrency-config.d.ts +1 -1
- package/dist/recall-disclosure-escalation.d.ts +1 -1
- package/dist/recall-explain-renderer.d.ts +1 -1
- package/dist/recall-explain-renderer.js +3 -3
- package/dist/recall-memory-map.d.ts +1 -1
- package/dist/recall-planner-llm.d.ts +1 -1
- package/dist/recall-state.d.ts +1 -1
- package/dist/recall-state.js +2 -2
- package/dist/recall-tag-filter.d.ts +1 -1
- package/dist/recall-timings.d.ts +1 -1
- package/dist/recall-xray-cli.d.ts +1 -1
- package/dist/recall-xray-cli.js +4 -4
- package/dist/recall-xray-renderer.d.ts +1 -1
- package/dist/recall-xray-renderer.js +3 -3
- package/dist/recall-xray.d.ts +1 -1
- package/dist/recall-xray.js +2 -2
- package/dist/replay/normalizers/chatgpt.d.ts +1 -1
- package/dist/replay/normalizers/claude.d.ts +1 -1
- package/dist/replay/normalizers/openclaw.d.ts +1 -1
- package/dist/replay/normalizers/shared.d.ts +1 -1
- package/dist/replay/runner.d.ts +1 -1
- package/dist/replay/types.d.ts +1 -1
- package/dist/replica-divergence.d.ts +279 -0
- package/dist/replica-divergence.js +28 -0
- package/dist/replica-divergence.js.map +1 -0
- package/dist/replica-peers-config.d.ts +5 -0
- package/dist/replica-peers-config.js +13 -0
- package/dist/replica-peers-config.js.map +1 -0
- package/dist/resolve-auth-token.d.ts +21 -2
- package/dist/resolve-auth-token.js +5 -1
- package/dist/resume-bundles.js +5 -3
- package/dist/retrieval-agents.d.ts +2 -2
- package/dist/retrieval-tiers.d.ts +1 -1
- package/dist/routing/engine.d.ts +1 -1
- package/dist/routing/store.d.ts +1 -1
- package/dist/salvage-envelope.d.ts +1 -1
- package/dist/schemas.d.ts +76 -76
- package/dist/{scope-profiles-D0-ff4xT.d.ts → scope-profiles-BVf3DZ7z.d.ts} +1 -1
- package/dist/search/embed-helper.d.ts +1 -1
- package/dist/search/factory.d.ts +1 -1
- package/dist/search/factory.js +8 -8
- package/dist/search/index.d.ts +1 -1
- package/dist/search/index.js +13 -13
- package/dist/search/lancedb-backend.d.ts +1 -1
- package/dist/search/lancedb-backend.js +2 -2
- package/dist/search/meilisearch-backend.d.ts +1 -1
- package/dist/search/meilisearch-backend.js +2 -2
- package/dist/search/noop-backend.d.ts +1 -1
- package/dist/search/orama-backend.d.ts +1 -1
- package/dist/search/orama-backend.js +2 -2
- package/dist/search/port.d.ts +1 -1
- package/dist/search/remote-backend.d.ts +1 -1
- package/dist/{semantic-consolidation-4Tfh7c1g.d.ts → semantic-consolidation-DXLV47y2.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +13 -11
- package/dist/semantic-rule-promotion.js +12 -10
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +12 -10
- package/dist/{service-Z8uzoAST.d.ts → service-DbgPTeMJ.d.ts} +2 -2
- package/dist/session-observer-bands.d.ts +1 -1
- package/dist/session-observer-state.d.ts +1 -1
- package/dist/shared-context/manager.d.ts +9 -9
- package/dist/signal.d.ts +1 -1
- package/dist/{storage-BZVMn8yJ.d.ts → storage-DOzTm8wG.d.ts} +13 -22
- package/dist/storage.d.ts +3 -2
- package/dist/storage.js +13 -10
- package/dist/summarizer.d.ts +1 -1
- package/dist/summary-snapshot.d.ts +1 -1
- package/dist/temporal-supersession.d.ts +3 -2
- package/dist/temporal-timeline-recall.d.ts +1 -1
- package/dist/temporal-validity.d.ts +1 -1
- package/dist/threading.d.ts +1 -1
- package/dist/tier-migration.d.ts +3 -2
- package/dist/tier-routing.d.ts +1 -1
- package/dist/topics.d.ts +1 -1
- package/dist/transcript.d.ts +1 -1
- package/dist/transfer/autodetect.js +1 -1
- package/dist/transfer/backup.js +2 -2
- package/dist/transfer/capsule-export.js +3 -3
- package/dist/transfer/capsule-import.js +2 -2
- package/dist/transfer/types.d.ts +66 -66
- package/dist/trust-score-stage.d.ts +1 -1
- package/dist/trust-score.d.ts +1 -1
- package/dist/{types-DHBxIF0s.d.ts → types-BA7W-_xy.d.ts} +53 -4
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/dist/utility-runtime.d.ts +1 -1
- package/dist/verified-recall.js +12 -10
- package/dist/write-envelope.d.ts +1 -1
- package/package.json +2 -2
- package/src/access-health-types.ts +8 -0
- package/src/access-http.test.ts +4 -0
- package/src/access-service-health.test.ts +23 -0
- package/src/access-service.ts +27 -4
- package/src/cli.ts +9 -7
- package/src/config.ts +2 -1
- package/src/corpus-watermark.test.ts +21 -0
- package/src/corpus-watermark.ts +25 -3
- package/src/entity-id-normalization.ts +55 -0
- package/src/entity-retrieval-boundaries.ts +134 -0
- package/src/entity-retrieval.ts +253 -27
- package/src/entity-schema.ts +2 -1
- package/src/memory-projection-mutations.ts +132 -0
- package/src/namespaces/storage.ts +5 -3
- package/src/operator-doctor-corpus.ts +41 -0
- package/src/operator-doctor-replica.test.ts +261 -0
- package/src/operator-doctor-replica.ts +129 -0
- package/src/operator-toolkit.ts +5 -4
- package/src/orchestration/orchestrator-init.ts +2 -2
- package/src/replica-divergence.test.ts +1475 -0
- package/src/replica-divergence.ts +910 -0
- package/src/replica-peers-config.ts +250 -0
- package/src/resolve-auth-token.ts +38 -0
- package/src/storage/entity-canonical-id-migration-adapter.ts +72 -0
- package/src/storage/entity-canonical-id-migration-runner.ts +68 -0
- package/src/storage/entity-canonical-id-migration.ts +904 -0
- package/src/storage/entity-store.ts +44 -0
- package/src/storage/memory-frontmatter-metadata.ts +17 -0
- package/src/storage/memory-migration-serialization.ts +37 -0
- package/src/storage/memory-read-store.ts +3 -2
- package/src/storage/profile-header.ts +898 -0
- package/src/storage-profile-timestamp.test.ts +3358 -0
- package/src/storage.ts +73 -77
- package/src/testing/subjects/profile-header-write.test.ts +71 -0
- package/src/types.ts +3 -3
- package/dist/chunk-2XP3PWL3.js.map +0 -1
- package/dist/chunk-4DJQYKMN.js.map +0 -1
- package/dist/chunk-E6EAB4J6.js.map +0 -1
- package/dist/chunk-EXM2Q555.js.map +0 -1
- package/dist/chunk-GJJBPI6I.js.map +0 -1
- package/dist/chunk-KPWLZBXZ.js.map +0 -1
- package/dist/chunk-LIIQPDFN.js.map +0 -1
- package/dist/chunk-UZK7VVPD.js.map +0 -1
- package/dist/chunk-V25OWXAC.js.map +0 -1
- package/dist/chunk-YN4AUGNF.js.map +0 -1
- /package/dist/{capsule-crypto-YO5QJ6L3.js.map → capsule-crypto-7FJQINUR.js.map} +0 -0
- /package/dist/{chunk-7RZJWZ46.js.map → chunk-227UFSEQ.js.map} +0 -0
- /package/dist/{chunk-VGEH3STZ.js.map → chunk-2BEJ4XCF.js.map} +0 -0
- /package/dist/{chunk-BXLOS5AJ.js.map → chunk-2NLLXCJG.js.map} +0 -0
- /package/dist/{chunk-6NNNURLK.js.map → chunk-2PHX3I5C.js.map} +0 -0
- /package/dist/{chunk-XAAIUXJO.js.map → chunk-3BSJBIZ3.js.map} +0 -0
- /package/dist/{chunk-JCVQV6QX.js.map → chunk-3UOSVTFR.js.map} +0 -0
- /package/dist/{chunk-AJ7QCSIS.js.map → chunk-7CPOZH6Y.js.map} +0 -0
- /package/dist/{chunk-M2KGXXTL.js.map → chunk-ALTP3MFS.js.map} +0 -0
- /package/dist/{chunk-77CNV7DY.js.map → chunk-BLAKS3CP.js.map} +0 -0
- /package/dist/{chunk-FE5FKTCI.js.map → chunk-BR4UFMLO.js.map} +0 -0
- /package/dist/{chunk-H2PBM562.js.map → chunk-DKQM5Q6Q.js.map} +0 -0
- /package/dist/{chunk-CSXHRAJJ.js.map → chunk-EG64LXKL.js.map} +0 -0
- /package/dist/{chunk-PTN6UAUA.js.map → chunk-ES36HTSC.js.map} +0 -0
- /package/dist/{chunk-WMDRLTNA.js.map → chunk-EZWWCN7I.js.map} +0 -0
- /package/dist/{chunk-XKGPIWGI.js.map → chunk-FBZTWUK5.js.map} +0 -0
- /package/dist/{chunk-Y6E37WRG.js.map → chunk-GJPCP5CA.js.map} +0 -0
- /package/dist/{chunk-VDX2J7OX.js.map → chunk-IPLYGWQF.js.map} +0 -0
- /package/dist/{chunk-XNUBF6RF.js.map → chunk-IYT632D5.js.map} +0 -0
- /package/dist/{chunk-2QSZNTDO.js.map → chunk-JBPKEARU.js.map} +0 -0
- /package/dist/{chunk-67DSDRUB.js.map → chunk-JIF3VRKQ.js.map} +0 -0
- /package/dist/{chunk-MIFF37NN.js.map → chunk-KKBTBUVT.js.map} +0 -0
- /package/dist/{chunk-ZHP4SUPB.js.map → chunk-LIHC3T43.js.map} +0 -0
- /package/dist/{chunk-UGH6VPPO.js.map → chunk-MWTMTW2T.js.map} +0 -0
- /package/dist/{chunk-F6LSNPPB.js.map → chunk-MXJ3VP6S.js.map} +0 -0
- /package/dist/{chunk-M5EPMNS4.js.map → chunk-P2CLYN6H.js.map} +0 -0
- /package/dist/{chunk-KGTD2KZJ.js.map → chunk-Q6YJW6KE.js.map} +0 -0
- /package/dist/{chunk-X3SQYAPP.js.map → chunk-R372P2EH.js.map} +0 -0
- /package/dist/{chunk-HBVOJNV4.js.map → chunk-RKXZ7P4V.js.map} +0 -0
- /package/dist/{chunk-RYHWZ52A.js.map → chunk-S2BBISHU.js.map} +0 -0
- /package/dist/{chunk-LLISELSY.js.map → chunk-TBMRQZDY.js.map} +0 -0
- /package/dist/{chunk-FMLKOL2Z.js.map → chunk-TC3VNKGX.js.map} +0 -0
- /package/dist/{chunk-7K5Q6COX.js.map → chunk-TVVEYCNW.js.map} +0 -0
- /package/dist/{chunk-NKDVXEFU.js.map → chunk-VFNI3RPT.js.map} +0 -0
- /package/dist/{chunk-PQPSZICJ.js.map → chunk-VYYNBJXH.js.map} +0 -0
- /package/dist/{chunk-SN3ZESWQ.js.map → chunk-WVVK2RFM.js.map} +0 -0
- /package/dist/{chunk-DQEMWVMT.js.map → chunk-X7Y7WX73.js.map} +0 -0
- /package/dist/{chunk-4KKV7W62.js.map → chunk-YOBNCD3Z.js.map} +0 -0
- /package/dist/{chunk-SK3EYPKC.js.map → chunk-ZN4UZWMT.js.map} +0 -0
- /package/dist/{first-start-migration-LEXL3XYL.js.map → first-start-migration-BKNGOFED.js.map} +0 -0
|
@@ -0,0 +1,3358 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
|
|
7
|
+
import { StorageManager } from "./storage.js";
|
|
8
|
+
|
|
9
|
+
const WRITE_TIME = "2030-01-02T03:04:05.000Z";
|
|
10
|
+
const STALE_HEADER = "*Last updated: 2024-01-02T03:04:05.000Z*";
|
|
11
|
+
const FRESH_HEADER = `*Last updated: ${WRITE_TIME}*`;
|
|
12
|
+
|
|
13
|
+
async function withMemoryDir(run: (dir: string) => Promise<void>): Promise<void> {
|
|
14
|
+
const dir = await mkdtemp(path.join(os.tmpdir(), "remnic-profile-timestamp-"));
|
|
15
|
+
try {
|
|
16
|
+
await run(dir);
|
|
17
|
+
} finally {
|
|
18
|
+
await rm(dir, { recursive: true, force: true });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
test("writeProfile refreshes a stale header at the write time", async (t) => {
|
|
23
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
24
|
+
try {
|
|
25
|
+
await withMemoryDir(async (dir) => {
|
|
26
|
+
const storage = new StorageManager(dir);
|
|
27
|
+
const staleProfile = [
|
|
28
|
+
"# Behavioral Profile",
|
|
29
|
+
"",
|
|
30
|
+
STALE_HEADER,
|
|
31
|
+
"",
|
|
32
|
+
"- Prefers concise status updates.",
|
|
33
|
+
"",
|
|
34
|
+
].join("\n");
|
|
35
|
+
|
|
36
|
+
await storage.writeProfile(staleProfile);
|
|
37
|
+
|
|
38
|
+
assert.equal(
|
|
39
|
+
await storage.readProfile(),
|
|
40
|
+
staleProfile.replace(STALE_HEADER, FRESH_HEADER),
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
} finally {
|
|
44
|
+
t.mock.timers.reset();
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("writeProfile adds one canonical header when content has none", async (t) => {
|
|
49
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
50
|
+
try {
|
|
51
|
+
await withMemoryDir(async (dir) => {
|
|
52
|
+
const storage = new StorageManager(dir);
|
|
53
|
+
|
|
54
|
+
await storage.writeProfile("# Behavioral Profile\n\n- Values direct communication.\n");
|
|
55
|
+
|
|
56
|
+
assert.equal(
|
|
57
|
+
await storage.readProfile(),
|
|
58
|
+
`# Behavioral Profile\n\n${FRESH_HEADER}\n\n- Values direct communication.\n`,
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
} finally {
|
|
62
|
+
t.mock.timers.reset();
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
test("writeProfile handles a title-only profile without a trailing newline", async (t) => {
|
|
66
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
67
|
+
try {
|
|
68
|
+
await withMemoryDir(async (dir) => {
|
|
69
|
+
const storage = new StorageManager(dir);
|
|
70
|
+
|
|
71
|
+
await storage.writeProfile("# Behavioral Profile");
|
|
72
|
+
|
|
73
|
+
assert.equal(await storage.readProfile(), `# Behavioral Profile\n${FRESH_HEADER}\n\n`);
|
|
74
|
+
});
|
|
75
|
+
} finally {
|
|
76
|
+
t.mock.timers.reset();
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
test("writeProfile preserves compound emphasis instead of replacing it", async (t) => {
|
|
80
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
81
|
+
try {
|
|
82
|
+
await withMemoryDir(async (dir) => {
|
|
83
|
+
const storage = new StorageManager(dir);
|
|
84
|
+
const compoundHeader = "*Last updated: source value* and *status: active*";
|
|
85
|
+
const profile = [
|
|
86
|
+
"# Behavioral Profile",
|
|
87
|
+
"",
|
|
88
|
+
compoundHeader,
|
|
89
|
+
"",
|
|
90
|
+
"- Keeps compound metadata.",
|
|
91
|
+
"",
|
|
92
|
+
].join("\n");
|
|
93
|
+
|
|
94
|
+
await storage.writeProfile(profile);
|
|
95
|
+
|
|
96
|
+
assert.equal(
|
|
97
|
+
await storage.readProfile(),
|
|
98
|
+
[
|
|
99
|
+
"# Behavioral Profile",
|
|
100
|
+
"",
|
|
101
|
+
FRESH_HEADER,
|
|
102
|
+
"",
|
|
103
|
+
compoundHeader,
|
|
104
|
+
"",
|
|
105
|
+
"- Keeps compound metadata.",
|
|
106
|
+
"",
|
|
107
|
+
].join("\n"),
|
|
108
|
+
);
|
|
109
|
+
});
|
|
110
|
+
} finally {
|
|
111
|
+
t.mock.timers.reset();
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("writeProfile does not treat ten-digit ordered markers as metadata boundaries", async (t) => {
|
|
116
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
117
|
+
try {
|
|
118
|
+
await withMemoryDir(async (dir) => {
|
|
119
|
+
const storage = new StorageManager(dir);
|
|
120
|
+
const proseHeader = "*Last updated: literal example*";
|
|
121
|
+
const profile = [
|
|
122
|
+
"# Behavioral Profile",
|
|
123
|
+
"",
|
|
124
|
+
proseHeader,
|
|
125
|
+
"1234567890. continuation",
|
|
126
|
+
"",
|
|
127
|
+
"- Keeps timestamp-shaped prose.",
|
|
128
|
+
"",
|
|
129
|
+
].join("\n");
|
|
130
|
+
|
|
131
|
+
await storage.writeProfile(profile);
|
|
132
|
+
|
|
133
|
+
assert.equal(
|
|
134
|
+
await storage.readProfile(),
|
|
135
|
+
[
|
|
136
|
+
"# Behavioral Profile",
|
|
137
|
+
"",
|
|
138
|
+
FRESH_HEADER,
|
|
139
|
+
"",
|
|
140
|
+
proseHeader,
|
|
141
|
+
"1234567890. continuation",
|
|
142
|
+
"",
|
|
143
|
+
"- Keeps timestamp-shaped prose.",
|
|
144
|
+
"",
|
|
145
|
+
].join("\n"),
|
|
146
|
+
);
|
|
147
|
+
});
|
|
148
|
+
} finally {
|
|
149
|
+
t.mock.timers.reset();
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
test("writeProfile recognizes an indented profile title", async (t) => {
|
|
153
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
154
|
+
try {
|
|
155
|
+
await withMemoryDir(async (dir) => {
|
|
156
|
+
const storage = new StorageManager(dir);
|
|
157
|
+
const profile = [
|
|
158
|
+
" # Behavioral Profile",
|
|
159
|
+
"",
|
|
160
|
+
"- Keeps the title boundary.",
|
|
161
|
+
"",
|
|
162
|
+
].join("\n");
|
|
163
|
+
|
|
164
|
+
await storage.writeProfile(profile);
|
|
165
|
+
|
|
166
|
+
assert.equal(
|
|
167
|
+
await storage.readProfile(),
|
|
168
|
+
[
|
|
169
|
+
" # Behavioral Profile",
|
|
170
|
+
"",
|
|
171
|
+
FRESH_HEADER,
|
|
172
|
+
"",
|
|
173
|
+
"- Keeps the title boundary.",
|
|
174
|
+
"",
|
|
175
|
+
].join("\n"),
|
|
176
|
+
);
|
|
177
|
+
});
|
|
178
|
+
} finally {
|
|
179
|
+
t.mock.timers.reset();
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test("writeProfile recognizes raw HTML terminators with trailing text", async (t) => {
|
|
184
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
185
|
+
try {
|
|
186
|
+
await withMemoryDir(async (dir) => {
|
|
187
|
+
const storage = new StorageManager(dir);
|
|
188
|
+
const profile = [
|
|
189
|
+
"# Behavioral Profile",
|
|
190
|
+
"",
|
|
191
|
+
"<pre>",
|
|
192
|
+
"Literal preformatted content.",
|
|
193
|
+
"code</pre> trailing text",
|
|
194
|
+
STALE_HEADER,
|
|
195
|
+
"",
|
|
196
|
+
"- Keeps metadata after raw HTML.",
|
|
197
|
+
"",
|
|
198
|
+
].join("\n");
|
|
199
|
+
|
|
200
|
+
await storage.writeProfile(profile);
|
|
201
|
+
|
|
202
|
+
assert.equal(await storage.readProfile(), profile.replace(STALE_HEADER, FRESH_HEADER));
|
|
203
|
+
});
|
|
204
|
+
} finally {
|
|
205
|
+
t.mock.timers.reset();
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
test("writeProfile closes raw HTML blocks with spaced end markers and trailing text", async (t) => {
|
|
209
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
210
|
+
try {
|
|
211
|
+
await withMemoryDir(async (dir) => {
|
|
212
|
+
const storage = new StorageManager(dir);
|
|
213
|
+
const profile = [
|
|
214
|
+
"<pre>",
|
|
215
|
+
"# Literal heading",
|
|
216
|
+
"*Last updated: literal example*",
|
|
217
|
+
"code </pre> trailing",
|
|
218
|
+
"",
|
|
219
|
+
"# Behavioral Profile",
|
|
220
|
+
"",
|
|
221
|
+
STALE_HEADER,
|
|
222
|
+
"",
|
|
223
|
+
"- Keeps the real profile metadata.",
|
|
224
|
+
"",
|
|
225
|
+
].join("\n");
|
|
226
|
+
|
|
227
|
+
await storage.writeProfile(profile);
|
|
228
|
+
|
|
229
|
+
assert.equal(await storage.readProfile(), profile.replace(STALE_HEADER, FRESH_HEADER));
|
|
230
|
+
});
|
|
231
|
+
} finally {
|
|
232
|
+
t.mock.timers.reset();
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
test("writeProfile keeps raw HTML opening-line content out of metadata scanning", async (t) => {
|
|
236
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
237
|
+
try {
|
|
238
|
+
await withMemoryDir(async (dir) => {
|
|
239
|
+
const storage = new StorageManager(dir);
|
|
240
|
+
const profile = [
|
|
241
|
+
"<pre>inline opener content",
|
|
242
|
+
"# Literal heading",
|
|
243
|
+
"*Last updated: literal example*",
|
|
244
|
+
"</pre>",
|
|
245
|
+
"# Behavioral Profile",
|
|
246
|
+
"",
|
|
247
|
+
STALE_HEADER,
|
|
248
|
+
"",
|
|
249
|
+
"- Keeps the real profile metadata.",
|
|
250
|
+
"",
|
|
251
|
+
].join("\n");
|
|
252
|
+
|
|
253
|
+
await storage.writeProfile(profile);
|
|
254
|
+
|
|
255
|
+
assert.equal(await storage.readProfile(), profile.replace(STALE_HEADER, FRESH_HEADER));
|
|
256
|
+
});
|
|
257
|
+
} finally {
|
|
258
|
+
t.mock.timers.reset();
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
test("writeProfile recognizes setext underlines as metadata boundaries", async (t) => {
|
|
262
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
263
|
+
try {
|
|
264
|
+
await withMemoryDir(async (dir) => {
|
|
265
|
+
const storage = new StorageManager(dir);
|
|
266
|
+
const profile = [
|
|
267
|
+
"# Behavioral Profile",
|
|
268
|
+
"",
|
|
269
|
+
"Section",
|
|
270
|
+
"=======",
|
|
271
|
+
STALE_HEADER,
|
|
272
|
+
"",
|
|
273
|
+
"- Keeps setext metadata compact.",
|
|
274
|
+
"",
|
|
275
|
+
].join("\n");
|
|
276
|
+
|
|
277
|
+
await storage.writeProfile(profile);
|
|
278
|
+
|
|
279
|
+
assert.equal(await storage.readProfile(), profile.replace(STALE_HEADER, FRESH_HEADER));
|
|
280
|
+
});
|
|
281
|
+
} finally {
|
|
282
|
+
t.mock.timers.reset();
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
test("writeProfile preserves timestamp prose before a setext underline", async (t) => {
|
|
286
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
287
|
+
try {
|
|
288
|
+
await withMemoryDir(async (dir) => {
|
|
289
|
+
const storage = new StorageManager(dir);
|
|
290
|
+
const profile = [
|
|
291
|
+
"# Behavioral Profile",
|
|
292
|
+
"",
|
|
293
|
+
"*Last updated: literal example*",
|
|
294
|
+
"===",
|
|
295
|
+
"Body after the setext heading.",
|
|
296
|
+
"",
|
|
297
|
+
].join("\n");
|
|
298
|
+
|
|
299
|
+
await storage.writeProfile(profile);
|
|
300
|
+
|
|
301
|
+
assert.equal(
|
|
302
|
+
await storage.readProfile(),
|
|
303
|
+
[
|
|
304
|
+
"# Behavioral Profile",
|
|
305
|
+
"",
|
|
306
|
+
FRESH_HEADER,
|
|
307
|
+
"",
|
|
308
|
+
"*Last updated: literal example*",
|
|
309
|
+
"===",
|
|
310
|
+
"Body after the setext heading.",
|
|
311
|
+
"",
|
|
312
|
+
].join("\n"),
|
|
313
|
+
);
|
|
314
|
+
});
|
|
315
|
+
} finally {
|
|
316
|
+
t.mock.timers.reset();
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
test("writeProfile preserves timestamp prose before a hyphen setext underline", async (t) => {
|
|
320
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
321
|
+
try {
|
|
322
|
+
await withMemoryDir(async (dir) => {
|
|
323
|
+
const storage = new StorageManager(dir);
|
|
324
|
+
const profile = [
|
|
325
|
+
"# Behavioral Profile",
|
|
326
|
+
"",
|
|
327
|
+
"*Last updated: literal example*",
|
|
328
|
+
"---",
|
|
329
|
+
"Body after the hyphen setext heading.",
|
|
330
|
+
"",
|
|
331
|
+
].join("\n");
|
|
332
|
+
|
|
333
|
+
await storage.writeProfile(profile);
|
|
334
|
+
|
|
335
|
+
assert.equal(
|
|
336
|
+
await storage.readProfile(),
|
|
337
|
+
[
|
|
338
|
+
"# Behavioral Profile",
|
|
339
|
+
"",
|
|
340
|
+
FRESH_HEADER,
|
|
341
|
+
"",
|
|
342
|
+
"*Last updated: literal example*",
|
|
343
|
+
"---",
|
|
344
|
+
"Body after the hyphen setext heading.",
|
|
345
|
+
"",
|
|
346
|
+
].join("\n"),
|
|
347
|
+
);
|
|
348
|
+
});
|
|
349
|
+
} finally {
|
|
350
|
+
t.mock.timers.reset();
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
test("writeProfile recognizes case-insensitive raw HTML terminators", async (t) => {
|
|
354
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
355
|
+
try {
|
|
356
|
+
await withMemoryDir(async (dir) => {
|
|
357
|
+
const storage = new StorageManager(dir);
|
|
358
|
+
const profile = [
|
|
359
|
+
"# Behavioral Profile",
|
|
360
|
+
"",
|
|
361
|
+
"<pre>",
|
|
362
|
+
"Literal preformatted content.",
|
|
363
|
+
"</PRE>",
|
|
364
|
+
STALE_HEADER,
|
|
365
|
+
"",
|
|
366
|
+
"- Keeps metadata after raw HTML.",
|
|
367
|
+
"",
|
|
368
|
+
].join("\n");
|
|
369
|
+
|
|
370
|
+
await storage.writeProfile(profile);
|
|
371
|
+
|
|
372
|
+
assert.equal(await storage.readProfile(), profile.replace(STALE_HEADER, FRESH_HEADER));
|
|
373
|
+
});
|
|
374
|
+
} finally {
|
|
375
|
+
t.mock.timers.reset();
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
test("writeProfile does not treat URI or email autolinks as HTML blocks", async (t) => {
|
|
379
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
380
|
+
try {
|
|
381
|
+
await withMemoryDir(async (dir) => {
|
|
382
|
+
const storage = new StorageManager(dir);
|
|
383
|
+
const profile = [
|
|
384
|
+
"<https://example.com>",
|
|
385
|
+
"<user@example.com>",
|
|
386
|
+
"# Behavioral Profile",
|
|
387
|
+
"",
|
|
388
|
+
STALE_HEADER,
|
|
389
|
+
"",
|
|
390
|
+
"- Keeps autolinks as prose.",
|
|
391
|
+
"",
|
|
392
|
+
].join("\n");
|
|
393
|
+
|
|
394
|
+
await storage.writeProfile(profile);
|
|
395
|
+
|
|
396
|
+
assert.equal(await storage.readProfile(), profile.replace(STALE_HEADER, FRESH_HEADER));
|
|
397
|
+
});
|
|
398
|
+
} finally {
|
|
399
|
+
t.mock.timers.reset();
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
test("writeProfile preserves case-sensitive CDATA block detection", async (t) => {
|
|
403
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
404
|
+
try {
|
|
405
|
+
await withMemoryDir(async (dir) => {
|
|
406
|
+
const storage = new StorageManager(dir);
|
|
407
|
+
const profile = [
|
|
408
|
+
"<![cdata[",
|
|
409
|
+
"# Behavioral Profile",
|
|
410
|
+
"",
|
|
411
|
+
STALE_HEADER,
|
|
412
|
+
"",
|
|
413
|
+
"- Keeps lowercase CDATA-like text as prose.",
|
|
414
|
+
"",
|
|
415
|
+
].join("\n");
|
|
416
|
+
|
|
417
|
+
await storage.writeProfile(profile);
|
|
418
|
+
|
|
419
|
+
assert.equal(await storage.readProfile(), profile.replace(STALE_HEADER, FRESH_HEADER));
|
|
420
|
+
});
|
|
421
|
+
} finally {
|
|
422
|
+
t.mock.timers.reset();
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
test("writeProfile recognizes thematic breaks as metadata boundaries", async (t) => {
|
|
426
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
427
|
+
try {
|
|
428
|
+
await withMemoryDir(async (dir) => {
|
|
429
|
+
const storage = new StorageManager(dir);
|
|
430
|
+
const profile = [
|
|
431
|
+
"# Behavioral Profile",
|
|
432
|
+
"",
|
|
433
|
+
"***",
|
|
434
|
+
STALE_HEADER,
|
|
435
|
+
"",
|
|
436
|
+
"- Keeps thematic-break metadata compact.",
|
|
437
|
+
"",
|
|
438
|
+
].join("\n");
|
|
439
|
+
|
|
440
|
+
await storage.writeProfile(profile);
|
|
441
|
+
|
|
442
|
+
assert.equal(await storage.readProfile(), profile.replace(STALE_HEADER, FRESH_HEADER));
|
|
443
|
+
});
|
|
444
|
+
} finally {
|
|
445
|
+
t.mock.timers.reset();
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
test("writeProfile recognizes empty headings and spaced thematic breaks", async (t) => {
|
|
449
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
450
|
+
try {
|
|
451
|
+
await withMemoryDir(async (dir) => {
|
|
452
|
+
const storage = new StorageManager(dir);
|
|
453
|
+
const profiles = [
|
|
454
|
+
[
|
|
455
|
+
"# Behavioral Profile",
|
|
456
|
+
"",
|
|
457
|
+
STALE_HEADER,
|
|
458
|
+
"###",
|
|
459
|
+
"Body after an empty heading.",
|
|
460
|
+
"",
|
|
461
|
+
].join("\n"),
|
|
462
|
+
[
|
|
463
|
+
"# Behavioral Profile",
|
|
464
|
+
"",
|
|
465
|
+
"- - -",
|
|
466
|
+
STALE_HEADER,
|
|
467
|
+
"",
|
|
468
|
+
"Body after a spaced thematic break.",
|
|
469
|
+
"",
|
|
470
|
+
].join("\n"),
|
|
471
|
+
[
|
|
472
|
+
"#",
|
|
473
|
+
STALE_HEADER,
|
|
474
|
+
"Body after an empty H1.",
|
|
475
|
+
"",
|
|
476
|
+
].join("\n"),
|
|
477
|
+
];
|
|
478
|
+
|
|
479
|
+
for (const profile of profiles) {
|
|
480
|
+
await storage.writeProfile(profile);
|
|
481
|
+
assert.equal(await storage.readProfile(), profile.replace(STALE_HEADER, FRESH_HEADER));
|
|
482
|
+
}
|
|
483
|
+
});
|
|
484
|
+
} finally {
|
|
485
|
+
t.mock.timers.reset();
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
test("writeProfile recognizes a compact header before prose", async (t) => {
|
|
489
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
490
|
+
try {
|
|
491
|
+
await withMemoryDir(async (dir) => {
|
|
492
|
+
const storage = new StorageManager(dir);
|
|
493
|
+
const profile = [
|
|
494
|
+
"# Behavioral Profile",
|
|
495
|
+
STALE_HEADER,
|
|
496
|
+
"Plain prose follows the compact header.",
|
|
497
|
+
"",
|
|
498
|
+
].join("\n");
|
|
499
|
+
|
|
500
|
+
await storage.writeProfile(profile);
|
|
501
|
+
|
|
502
|
+
assert.equal(await storage.readProfile(), profile.replace(STALE_HEADER, FRESH_HEADER));
|
|
503
|
+
});
|
|
504
|
+
} finally {
|
|
505
|
+
t.mock.timers.reset();
|
|
506
|
+
}
|
|
507
|
+
});
|
|
508
|
+
test("writeProfile does not use nested list headings as the profile title", async (t) => {
|
|
509
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
510
|
+
try {
|
|
511
|
+
await withMemoryDir(async (dir) => {
|
|
512
|
+
const storage = new StorageManager(dir);
|
|
513
|
+
const profile = [
|
|
514
|
+
"- List item",
|
|
515
|
+
" # Nested heading",
|
|
516
|
+
" Nested body text.",
|
|
517
|
+
"",
|
|
518
|
+
].join("\n");
|
|
519
|
+
|
|
520
|
+
await storage.writeProfile(profile);
|
|
521
|
+
|
|
522
|
+
assert.equal(await storage.readProfile(), `${FRESH_HEADER}\n\n${profile}`);
|
|
523
|
+
});
|
|
524
|
+
} finally {
|
|
525
|
+
t.mock.timers.reset();
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
test("writeProfile does not use loose-list headings as the profile title", async (t) => {
|
|
529
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
530
|
+
try {
|
|
531
|
+
await withMemoryDir(async (dir) => {
|
|
532
|
+
const storage = new StorageManager(dir);
|
|
533
|
+
const profile = [
|
|
534
|
+
"- List item",
|
|
535
|
+
"",
|
|
536
|
+
" # Nested heading",
|
|
537
|
+
" Nested body text.",
|
|
538
|
+
"",
|
|
539
|
+
].join("\n");
|
|
540
|
+
|
|
541
|
+
await storage.writeProfile(profile);
|
|
542
|
+
|
|
543
|
+
assert.equal(await storage.readProfile(), `${FRESH_HEADER}\n\n${profile}`);
|
|
544
|
+
});
|
|
545
|
+
} finally {
|
|
546
|
+
t.mock.timers.reset();
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
test("writeProfile rejects malformed HTML attributes before the profile title", async (t) => {
|
|
550
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
551
|
+
try {
|
|
552
|
+
await withMemoryDir(async (dir) => {
|
|
553
|
+
const storage = new StorageManager(dir);
|
|
554
|
+
const profile = [
|
|
555
|
+
"<custom / nope>",
|
|
556
|
+
"# Behavioral Profile",
|
|
557
|
+
STALE_HEADER,
|
|
558
|
+
"",
|
|
559
|
+
"- Keeps malformed tag-shaped text as prose.",
|
|
560
|
+
"",
|
|
561
|
+
].join("\n");
|
|
562
|
+
|
|
563
|
+
await storage.writeProfile(profile);
|
|
564
|
+
|
|
565
|
+
assert.equal(await storage.readProfile(), profile.replace(STALE_HEADER, FRESH_HEADER));
|
|
566
|
+
});
|
|
567
|
+
} finally {
|
|
568
|
+
t.mock.timers.reset();
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
test("writeProfile keeps malformed built-in HTML blocks opaque before the profile title", async (t) => {
|
|
572
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
573
|
+
try {
|
|
574
|
+
await withMemoryDir(async (dir) => {
|
|
575
|
+
const storage = new StorageManager(dir);
|
|
576
|
+
const profile = [
|
|
577
|
+
"<div / nope>",
|
|
578
|
+
"# Behavioral Profile",
|
|
579
|
+
STALE_HEADER,
|
|
580
|
+
"",
|
|
581
|
+
"- Keeps malformed built-in tag-shaped text as prose.",
|
|
582
|
+
"",
|
|
583
|
+
].join("\n");
|
|
584
|
+
|
|
585
|
+
await storage.writeProfile(profile);
|
|
586
|
+
|
|
587
|
+
assert.equal(await storage.readProfile(), `${FRESH_HEADER}\n\n${profile}`);
|
|
588
|
+
});
|
|
589
|
+
} finally {
|
|
590
|
+
t.mock.timers.reset();
|
|
591
|
+
}
|
|
592
|
+
});
|
|
593
|
+
test("writeProfile recognizes block quotes as metadata boundaries", async (t) => {
|
|
594
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
595
|
+
try {
|
|
596
|
+
await withMemoryDir(async (dir) => {
|
|
597
|
+
const storage = new StorageManager(dir);
|
|
598
|
+
const profile = [
|
|
599
|
+
"# Behavioral Profile",
|
|
600
|
+
"",
|
|
601
|
+
"- Existing profile detail.",
|
|
602
|
+
"",
|
|
603
|
+
STALE_HEADER,
|
|
604
|
+
"> Quoted profile detail.",
|
|
605
|
+
"",
|
|
606
|
+
].join("\n");
|
|
607
|
+
|
|
608
|
+
await storage.writeProfile(profile);
|
|
609
|
+
|
|
610
|
+
assert.equal(await storage.readProfile(), profile.replace(STALE_HEADER, FRESH_HEADER));
|
|
611
|
+
});
|
|
612
|
+
} finally {
|
|
613
|
+
t.mock.timers.reset();
|
|
614
|
+
}
|
|
615
|
+
});
|
|
616
|
+
test("writeProfile removes adjacent compact duplicate headers", async (t) => {
|
|
617
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
618
|
+
try {
|
|
619
|
+
await withMemoryDir(async (dir) => {
|
|
620
|
+
const storage = new StorageManager(dir);
|
|
621
|
+
const secondStaleHeader = "*Last updated: 2025-01-02T03:04:05.000Z*";
|
|
622
|
+
const profile = [
|
|
623
|
+
"# Behavioral Profile",
|
|
624
|
+
STALE_HEADER,
|
|
625
|
+
secondStaleHeader,
|
|
626
|
+
"Plain prose follows adjacent headers.",
|
|
627
|
+
"",
|
|
628
|
+
].join("\n");
|
|
629
|
+
|
|
630
|
+
await storage.writeProfile(profile);
|
|
631
|
+
|
|
632
|
+
assert.equal(
|
|
633
|
+
await storage.readProfile(),
|
|
634
|
+
["# Behavioral Profile", FRESH_HEADER, "Plain prose follows adjacent headers.", ""].join("\n"),
|
|
635
|
+
);
|
|
636
|
+
});
|
|
637
|
+
} finally {
|
|
638
|
+
t.mock.timers.reset();
|
|
639
|
+
}
|
|
640
|
+
});
|
|
641
|
+
test("writeProfile keeps an inserted header standalone", async (t) => {
|
|
642
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
643
|
+
try {
|
|
644
|
+
await withMemoryDir(async (dir) => {
|
|
645
|
+
const storage = new StorageManager(dir);
|
|
646
|
+
await storage.writeProfile("# Behavioral Profile\n- Values direct communication.\n");
|
|
647
|
+
|
|
648
|
+
const firstWrite = await storage.readProfile();
|
|
649
|
+
await storage.writeProfile(firstWrite);
|
|
650
|
+
|
|
651
|
+
assert.equal(await storage.readProfile(), firstWrite);
|
|
652
|
+
});
|
|
653
|
+
} finally {
|
|
654
|
+
t.mock.timers.reset();
|
|
655
|
+
}
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
test("writeProfile preserves CRLF line endings", async (t) => {
|
|
659
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
660
|
+
try {
|
|
661
|
+
await withMemoryDir(async (dir) => {
|
|
662
|
+
const storage = new StorageManager(dir);
|
|
663
|
+
|
|
664
|
+
await storage.writeProfile("# Behavioral Profile\r\n\r\n- Values direct communication.\r\n");
|
|
665
|
+
|
|
666
|
+
assert.equal(
|
|
667
|
+
await storage.readProfile(),
|
|
668
|
+
`# Behavioral Profile\r\n\r\n${FRESH_HEADER}\r\n\r\n- Values direct communication.\r\n`,
|
|
669
|
+
);
|
|
670
|
+
});
|
|
671
|
+
} finally {
|
|
672
|
+
t.mock.timers.reset();
|
|
673
|
+
}
|
|
674
|
+
});
|
|
675
|
+
test("writeProfile preserves mixed existing line endings", async (t) => {
|
|
676
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
677
|
+
try {
|
|
678
|
+
await withMemoryDir(async (dir) => {
|
|
679
|
+
const storage = new StorageManager(dir);
|
|
680
|
+
const profile = `# Behavioral Profile\r\n\n${STALE_HEADER}\r\n\n- Keeps original separators.\r\n`;
|
|
681
|
+
|
|
682
|
+
await storage.writeProfile(profile);
|
|
683
|
+
|
|
684
|
+
assert.equal(await storage.readProfile(), profile.replace(STALE_HEADER, FRESH_HEADER));
|
|
685
|
+
});
|
|
686
|
+
} finally {
|
|
687
|
+
t.mock.timers.reset();
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
test("writeProfile preserves standalone carriage-return line endings", async (t) => {
|
|
692
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
693
|
+
try {
|
|
694
|
+
await withMemoryDir(async (dir) => {
|
|
695
|
+
const storage = new StorageManager(dir);
|
|
696
|
+
const profile = `# Behavioral Profile\r\r${STALE_HEADER}\r\r- Keeps original separators.\r`;
|
|
697
|
+
|
|
698
|
+
await storage.writeProfile(profile);
|
|
699
|
+
|
|
700
|
+
assert.equal(await storage.readProfile(), profile.replace(STALE_HEADER, FRESH_HEADER));
|
|
701
|
+
});
|
|
702
|
+
} finally {
|
|
703
|
+
t.mock.timers.reset();
|
|
704
|
+
}
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
test("writeProfile ignores HTML comment markers inside fenced code", async (t) => {
|
|
708
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
709
|
+
try {
|
|
710
|
+
await withMemoryDir(async (dir) => {
|
|
711
|
+
const storage = new StorageManager(dir);
|
|
712
|
+
const profile = [
|
|
713
|
+
"```markdown",
|
|
714
|
+
"<!--",
|
|
715
|
+
"*Last updated: literal example*",
|
|
716
|
+
"```",
|
|
717
|
+
"",
|
|
718
|
+
"# Behavioral Profile",
|
|
719
|
+
"",
|
|
720
|
+
STALE_HEADER,
|
|
721
|
+
"",
|
|
722
|
+
"- Keeps fenced literal content.",
|
|
723
|
+
"",
|
|
724
|
+
].join("\n");
|
|
725
|
+
|
|
726
|
+
await storage.writeProfile(profile);
|
|
727
|
+
|
|
728
|
+
assert.equal(await storage.readProfile(), profile.replace(STALE_HEADER, FRESH_HEADER));
|
|
729
|
+
});
|
|
730
|
+
} finally {
|
|
731
|
+
t.mock.timers.reset();
|
|
732
|
+
}
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
test("writeProfile preserves timestamp-shaped ordinary HTML blocks", async (t) => {
|
|
736
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
737
|
+
try {
|
|
738
|
+
await withMemoryDir(async (dir) => {
|
|
739
|
+
const storage = new StorageManager(dir);
|
|
740
|
+
for (const tag of ["div", "table"]) {
|
|
741
|
+
const profile = [
|
|
742
|
+
"# Behavioral Profile",
|
|
743
|
+
"",
|
|
744
|
+
`<${tag}>`,
|
|
745
|
+
"*Last updated: literal example*",
|
|
746
|
+
`</${tag}>`,
|
|
747
|
+
"",
|
|
748
|
+
"- Keeps HTML examples.",
|
|
749
|
+
"",
|
|
750
|
+
].join("\n");
|
|
751
|
+
|
|
752
|
+
await storage.writeProfile(profile);
|
|
753
|
+
|
|
754
|
+
assert.equal(
|
|
755
|
+
await storage.readProfile(),
|
|
756
|
+
profile.replace(`\n\n<${tag}>`, `\n\n${FRESH_HEADER}\n\n<${tag}>`),
|
|
757
|
+
);
|
|
758
|
+
}
|
|
759
|
+
});
|
|
760
|
+
} finally {
|
|
761
|
+
t.mock.timers.reset();
|
|
762
|
+
}
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
test("writeProfile preserves timestamp-shaped nested list content", async (t) => {
|
|
766
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
767
|
+
try {
|
|
768
|
+
await withMemoryDir(async (dir) => {
|
|
769
|
+
const storage = new StorageManager(dir);
|
|
770
|
+
const profile = [
|
|
771
|
+
"# Behavioral Profile",
|
|
772
|
+
"",
|
|
773
|
+
" - *Last updated: literal example*",
|
|
774
|
+
"",
|
|
775
|
+
"- Keeps list content.",
|
|
776
|
+
"",
|
|
777
|
+
].join("\n");
|
|
778
|
+
|
|
779
|
+
await storage.writeProfile(profile);
|
|
780
|
+
|
|
781
|
+
assert.equal(
|
|
782
|
+
await storage.readProfile(),
|
|
783
|
+
[
|
|
784
|
+
"# Behavioral Profile",
|
|
785
|
+
"",
|
|
786
|
+
FRESH_HEADER,
|
|
787
|
+
"",
|
|
788
|
+
" - *Last updated: literal example*",
|
|
789
|
+
"",
|
|
790
|
+
"- Keeps list content.",
|
|
791
|
+
"",
|
|
792
|
+
].join("\n"),
|
|
793
|
+
);
|
|
794
|
+
});
|
|
795
|
+
} finally {
|
|
796
|
+
t.mock.timers.reset();
|
|
797
|
+
}
|
|
798
|
+
});
|
|
799
|
+
test("writeProfile recognizes a completed indented code block before metadata", async (t) => {
|
|
800
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
801
|
+
try {
|
|
802
|
+
await withMemoryDir(async (dir) => {
|
|
803
|
+
const storage = new StorageManager(dir);
|
|
804
|
+
const profile = [
|
|
805
|
+
"# Behavioral Profile",
|
|
806
|
+
"",
|
|
807
|
+
" code example",
|
|
808
|
+
STALE_HEADER,
|
|
809
|
+
"",
|
|
810
|
+
"- Keeps metadata after indented code.",
|
|
811
|
+
"",
|
|
812
|
+
].join("\n");
|
|
813
|
+
|
|
814
|
+
await storage.writeProfile(profile);
|
|
815
|
+
|
|
816
|
+
assert.equal(await storage.readProfile(), profile.replace(STALE_HEADER, FRESH_HEADER));
|
|
817
|
+
});
|
|
818
|
+
} finally {
|
|
819
|
+
t.mock.timers.reset();
|
|
820
|
+
}
|
|
821
|
+
});
|
|
822
|
+
test("writeProfile treats generic HTML after completed indented code as opaque", async (t) => {
|
|
823
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
824
|
+
try {
|
|
825
|
+
await withMemoryDir(async (dir) => {
|
|
826
|
+
const storage = new StorageManager(dir);
|
|
827
|
+
const profile = [
|
|
828
|
+
"# Behavioral Profile",
|
|
829
|
+
"",
|
|
830
|
+
" code example",
|
|
831
|
+
"<custom>",
|
|
832
|
+
"# Embedded heading",
|
|
833
|
+
"",
|
|
834
|
+
"*Last updated: literal inside HTML*",
|
|
835
|
+
"</custom>",
|
|
836
|
+
"",
|
|
837
|
+
STALE_HEADER,
|
|
838
|
+
"",
|
|
839
|
+
"- Keeps metadata after generic HTML.",
|
|
840
|
+
"",
|
|
841
|
+
].join("\n");
|
|
842
|
+
|
|
843
|
+
await storage.writeProfile(profile);
|
|
844
|
+
|
|
845
|
+
assert.equal(
|
|
846
|
+
await storage.readProfile(),
|
|
847
|
+
profile.replace(STALE_HEADER, FRESH_HEADER),
|
|
848
|
+
);
|
|
849
|
+
});
|
|
850
|
+
} finally {
|
|
851
|
+
t.mock.timers.reset();
|
|
852
|
+
}
|
|
853
|
+
});
|
|
854
|
+
test("writeProfile recognizes standalone headers with Markdown indentation", async (t) => {
|
|
855
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
856
|
+
try {
|
|
857
|
+
await withMemoryDir(async (dir) => {
|
|
858
|
+
const storage = new StorageManager(dir);
|
|
859
|
+
const profile = [
|
|
860
|
+
"# Behavioral Profile",
|
|
861
|
+
"",
|
|
862
|
+
` ${STALE_HEADER}`,
|
|
863
|
+
"",
|
|
864
|
+
"- Keeps the indented metadata header canonical.",
|
|
865
|
+
"",
|
|
866
|
+
].join("\n");
|
|
867
|
+
|
|
868
|
+
await storage.writeProfile(profile);
|
|
869
|
+
|
|
870
|
+
assert.equal(
|
|
871
|
+
await storage.readProfile(),
|
|
872
|
+
[
|
|
873
|
+
"# Behavioral Profile",
|
|
874
|
+
"",
|
|
875
|
+
FRESH_HEADER,
|
|
876
|
+
"",
|
|
877
|
+
"- Keeps the indented metadata header canonical.",
|
|
878
|
+
"",
|
|
879
|
+
].join("\n"),
|
|
880
|
+
);
|
|
881
|
+
});
|
|
882
|
+
} finally {
|
|
883
|
+
t.mock.timers.reset();
|
|
884
|
+
}
|
|
885
|
+
});
|
|
886
|
+
|
|
887
|
+
test("writeProfile preserves indented metadata inside loose list items", async (t) => {
|
|
888
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
889
|
+
try {
|
|
890
|
+
await withMemoryDir(async (dir) => {
|
|
891
|
+
const storage = new StorageManager(dir);
|
|
892
|
+
const profile = [
|
|
893
|
+
"# Behavioral Profile",
|
|
894
|
+
"",
|
|
895
|
+
"- item",
|
|
896
|
+
" continuation",
|
|
897
|
+
"",
|
|
898
|
+
` ${STALE_HEADER}`,
|
|
899
|
+
"",
|
|
900
|
+
"- Keeps loose-list content.",
|
|
901
|
+
"",
|
|
902
|
+
].join("\n");
|
|
903
|
+
await storage.writeProfile(profile);
|
|
904
|
+
|
|
905
|
+
assert.equal(
|
|
906
|
+
await storage.readProfile(),
|
|
907
|
+
[
|
|
908
|
+
"# Behavioral Profile",
|
|
909
|
+
"",
|
|
910
|
+
FRESH_HEADER,
|
|
911
|
+
"",
|
|
912
|
+
"- item",
|
|
913
|
+
" continuation",
|
|
914
|
+
"",
|
|
915
|
+
` ${STALE_HEADER}`,
|
|
916
|
+
"",
|
|
917
|
+
"- Keeps loose-list content.",
|
|
918
|
+
"",
|
|
919
|
+
].join("\n"),
|
|
920
|
+
);
|
|
921
|
+
});
|
|
922
|
+
} finally {
|
|
923
|
+
t.mock.timers.reset();
|
|
924
|
+
}
|
|
925
|
+
});
|
|
926
|
+
|
|
927
|
+
test("writeProfile preserves metadata inside lists with lazy continuations", async (t) => {
|
|
928
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
929
|
+
try {
|
|
930
|
+
await withMemoryDir(async (dir) => {
|
|
931
|
+
const storage = new StorageManager(dir);
|
|
932
|
+
const profile = [
|
|
933
|
+
"# Behavioral Profile",
|
|
934
|
+
"",
|
|
935
|
+
"- item",
|
|
936
|
+
"lazy continuation",
|
|
937
|
+
"",
|
|
938
|
+
` ${STALE_HEADER}`,
|
|
939
|
+
"",
|
|
940
|
+
"- Keeps lazy-list content.",
|
|
941
|
+
"",
|
|
942
|
+
].join("\n");
|
|
943
|
+
|
|
944
|
+
await storage.writeProfile(profile);
|
|
945
|
+
|
|
946
|
+
assert.equal(
|
|
947
|
+
await storage.readProfile(),
|
|
948
|
+
[
|
|
949
|
+
"# Behavioral Profile",
|
|
950
|
+
"",
|
|
951
|
+
FRESH_HEADER,
|
|
952
|
+
"",
|
|
953
|
+
"- item",
|
|
954
|
+
"lazy continuation",
|
|
955
|
+
"",
|
|
956
|
+
` ${STALE_HEADER}`,
|
|
957
|
+
"",
|
|
958
|
+
"- Keeps lazy-list content.",
|
|
959
|
+
"",
|
|
960
|
+
].join("\n"),
|
|
961
|
+
);
|
|
962
|
+
});
|
|
963
|
+
} finally {
|
|
964
|
+
t.mock.timers.reset();
|
|
965
|
+
}
|
|
966
|
+
});
|
|
967
|
+
test("writeProfile carries list context across lazy continuations", async (t) => {
|
|
968
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
969
|
+
try {
|
|
970
|
+
await withMemoryDir(async (dir) => {
|
|
971
|
+
const storage = new StorageManager(dir);
|
|
972
|
+
const profile = [
|
|
973
|
+
"# Behavioral Profile",
|
|
974
|
+
"",
|
|
975
|
+
"- item",
|
|
976
|
+
"lazy one",
|
|
977
|
+
"lazy two",
|
|
978
|
+
"",
|
|
979
|
+
` ${STALE_HEADER}`,
|
|
980
|
+
"",
|
|
981
|
+
"- Keeps multi-line lazy-list content.",
|
|
982
|
+
"",
|
|
983
|
+
].join("\n");
|
|
984
|
+
|
|
985
|
+
await storage.writeProfile(profile);
|
|
986
|
+
|
|
987
|
+
assert.equal(
|
|
988
|
+
await storage.readProfile(),
|
|
989
|
+
[
|
|
990
|
+
"# Behavioral Profile",
|
|
991
|
+
"",
|
|
992
|
+
FRESH_HEADER,
|
|
993
|
+
"",
|
|
994
|
+
"- item",
|
|
995
|
+
"lazy one",
|
|
996
|
+
"lazy two",
|
|
997
|
+
"",
|
|
998
|
+
` ${STALE_HEADER}`,
|
|
999
|
+
"",
|
|
1000
|
+
"- Keeps multi-line lazy-list content.",
|
|
1001
|
+
"",
|
|
1002
|
+
].join("\n"),
|
|
1003
|
+
);
|
|
1004
|
+
});
|
|
1005
|
+
} finally {
|
|
1006
|
+
t.mock.timers.reset();
|
|
1007
|
+
}
|
|
1008
|
+
});
|
|
1009
|
+
test("writeProfile ends a one-line HTML declaration before finding the title", async (t) => {
|
|
1010
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
1011
|
+
try {
|
|
1012
|
+
await withMemoryDir(async (dir) => {
|
|
1013
|
+
const storage = new StorageManager(dir);
|
|
1014
|
+
const profile = [
|
|
1015
|
+
"<!DOCTYPE html>",
|
|
1016
|
+
"# Behavioral Profile",
|
|
1017
|
+
"",
|
|
1018
|
+
"- Keeps declaration content.",
|
|
1019
|
+
"",
|
|
1020
|
+
].join("\n");
|
|
1021
|
+
|
|
1022
|
+
await storage.writeProfile(profile);
|
|
1023
|
+
|
|
1024
|
+
assert.equal(
|
|
1025
|
+
await storage.readProfile(),
|
|
1026
|
+
[
|
|
1027
|
+
"<!DOCTYPE html>",
|
|
1028
|
+
"# Behavioral Profile",
|
|
1029
|
+
"",
|
|
1030
|
+
FRESH_HEADER,
|
|
1031
|
+
"",
|
|
1032
|
+
"- Keeps declaration content.",
|
|
1033
|
+
"",
|
|
1034
|
+
].join("\n"),
|
|
1035
|
+
);
|
|
1036
|
+
});
|
|
1037
|
+
} finally {
|
|
1038
|
+
t.mock.timers.reset();
|
|
1039
|
+
}
|
|
1040
|
+
});
|
|
1041
|
+
|
|
1042
|
+
test("writeProfile treats a single-line declaration as a metadata boundary", async (t) => {
|
|
1043
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
1044
|
+
try {
|
|
1045
|
+
await withMemoryDir(async (dir) => {
|
|
1046
|
+
const storage = new StorageManager(dir);
|
|
1047
|
+
const profile = [
|
|
1048
|
+
"<!DOCTYPE html>",
|
|
1049
|
+
STALE_HEADER,
|
|
1050
|
+
"",
|
|
1051
|
+
"- Keeps declaration metadata.",
|
|
1052
|
+
"",
|
|
1053
|
+
].join("\n");
|
|
1054
|
+
|
|
1055
|
+
await storage.writeProfile(profile);
|
|
1056
|
+
|
|
1057
|
+
assert.equal(await storage.readProfile(), profile.replace(STALE_HEADER, FRESH_HEADER));
|
|
1058
|
+
});
|
|
1059
|
+
} finally {
|
|
1060
|
+
t.mock.timers.reset();
|
|
1061
|
+
}
|
|
1062
|
+
});
|
|
1063
|
+
|
|
1064
|
+
test("writeProfile keeps nested same-name HTML blocks opaque", async (t) => {
|
|
1065
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
1066
|
+
try {
|
|
1067
|
+
await withMemoryDir(async (dir) => {
|
|
1068
|
+
const storage = new StorageManager(dir);
|
|
1069
|
+
const profile = [
|
|
1070
|
+
"# Behavioral Profile",
|
|
1071
|
+
"",
|
|
1072
|
+
"<div>",
|
|
1073
|
+
"<div></div>",
|
|
1074
|
+
"*Last updated: literal example*",
|
|
1075
|
+
"</div>",
|
|
1076
|
+
"",
|
|
1077
|
+
"- Keeps nested HTML.",
|
|
1078
|
+
"",
|
|
1079
|
+
].join("\n");
|
|
1080
|
+
|
|
1081
|
+
await storage.writeProfile(profile);
|
|
1082
|
+
|
|
1083
|
+
assert.equal(
|
|
1084
|
+
await storage.readProfile(),
|
|
1085
|
+
[
|
|
1086
|
+
"# Behavioral Profile",
|
|
1087
|
+
"",
|
|
1088
|
+
FRESH_HEADER,
|
|
1089
|
+
"",
|
|
1090
|
+
"<div>",
|
|
1091
|
+
"<div></div>",
|
|
1092
|
+
"*Last updated: literal example*",
|
|
1093
|
+
"</div>",
|
|
1094
|
+
"",
|
|
1095
|
+
"- Keeps nested HTML.",
|
|
1096
|
+
"",
|
|
1097
|
+
].join("\n"),
|
|
1098
|
+
);
|
|
1099
|
+
});
|
|
1100
|
+
} finally {
|
|
1101
|
+
t.mock.timers.reset();
|
|
1102
|
+
}
|
|
1103
|
+
});
|
|
1104
|
+
|
|
1105
|
+
test("writeProfile keeps metadata inside raw HTML blocks with end-of-line openers", async (t) => {
|
|
1106
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
1107
|
+
try {
|
|
1108
|
+
await withMemoryDir(async (dir) => {
|
|
1109
|
+
const storage = new StorageManager(dir);
|
|
1110
|
+
const profile = [
|
|
1111
|
+
"<pre",
|
|
1112
|
+
"# Literal heading",
|
|
1113
|
+
"*Last updated: literal*",
|
|
1114
|
+
"</pre>",
|
|
1115
|
+
"",
|
|
1116
|
+
"# Behavioral Profile",
|
|
1117
|
+
"",
|
|
1118
|
+
STALE_HEADER,
|
|
1119
|
+
"",
|
|
1120
|
+
"- Keeps raw HTML content opaque.",
|
|
1121
|
+
"",
|
|
1122
|
+
].join("\n");
|
|
1123
|
+
|
|
1124
|
+
await storage.writeProfile(profile);
|
|
1125
|
+
|
|
1126
|
+
assert.equal(
|
|
1127
|
+
await storage.readProfile(),
|
|
1128
|
+
[
|
|
1129
|
+
"<pre",
|
|
1130
|
+
"# Literal heading",
|
|
1131
|
+
"*Last updated: literal*",
|
|
1132
|
+
"</pre>",
|
|
1133
|
+
"",
|
|
1134
|
+
"# Behavioral Profile",
|
|
1135
|
+
"",
|
|
1136
|
+
FRESH_HEADER,
|
|
1137
|
+
"",
|
|
1138
|
+
"- Keeps raw HTML content opaque.",
|
|
1139
|
+
"",
|
|
1140
|
+
].join("\n"),
|
|
1141
|
+
);
|
|
1142
|
+
});
|
|
1143
|
+
} finally {
|
|
1144
|
+
t.mock.timers.reset();
|
|
1145
|
+
}
|
|
1146
|
+
});
|
|
1147
|
+
|
|
1148
|
+
test("writeProfile treats HTML openers as metadata boundaries", async (t) => {
|
|
1149
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
1150
|
+
try {
|
|
1151
|
+
await withMemoryDir(async (dir) => {
|
|
1152
|
+
const storage = new StorageManager(dir);
|
|
1153
|
+
const profile = [
|
|
1154
|
+
"# Behavioral Profile",
|
|
1155
|
+
"",
|
|
1156
|
+
STALE_HEADER,
|
|
1157
|
+
"<div>",
|
|
1158
|
+
"HTML content.",
|
|
1159
|
+
"</div>",
|
|
1160
|
+
"",
|
|
1161
|
+
"- Keeps the HTML block after the header.",
|
|
1162
|
+
"",
|
|
1163
|
+
].join("\n");
|
|
1164
|
+
|
|
1165
|
+
await storage.writeProfile(profile);
|
|
1166
|
+
|
|
1167
|
+
assert.equal(
|
|
1168
|
+
await storage.readProfile(),
|
|
1169
|
+
profile.replace(STALE_HEADER, FRESH_HEADER),
|
|
1170
|
+
);
|
|
1171
|
+
});
|
|
1172
|
+
} finally {
|
|
1173
|
+
t.mock.timers.reset();
|
|
1174
|
+
}
|
|
1175
|
+
});
|
|
1176
|
+
|
|
1177
|
+
test("writeProfile recognizes indented Markdown block boundaries", async (t) => {
|
|
1178
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
1179
|
+
try {
|
|
1180
|
+
await withMemoryDir(async (dir) => {
|
|
1181
|
+
const storage = new StorageManager(dir);
|
|
1182
|
+
const profile = [
|
|
1183
|
+
"# Behavioral Profile",
|
|
1184
|
+
"",
|
|
1185
|
+
STALE_HEADER,
|
|
1186
|
+
" - list item",
|
|
1187
|
+
STALE_HEADER,
|
|
1188
|
+
" > block quote",
|
|
1189
|
+
STALE_HEADER,
|
|
1190
|
+
" ***",
|
|
1191
|
+
"",
|
|
1192
|
+
"- Keeps indented Markdown blocks.",
|
|
1193
|
+
"",
|
|
1194
|
+
].join("\n");
|
|
1195
|
+
|
|
1196
|
+
await storage.writeProfile(profile);
|
|
1197
|
+
|
|
1198
|
+
assert.equal(
|
|
1199
|
+
await storage.readProfile(),
|
|
1200
|
+
[
|
|
1201
|
+
"# Behavioral Profile",
|
|
1202
|
+
"",
|
|
1203
|
+
FRESH_HEADER,
|
|
1204
|
+
" - list item",
|
|
1205
|
+
" > block quote",
|
|
1206
|
+
" ***",
|
|
1207
|
+
"",
|
|
1208
|
+
"- Keeps indented Markdown blocks.",
|
|
1209
|
+
"",
|
|
1210
|
+
].join("\n"),
|
|
1211
|
+
);
|
|
1212
|
+
});
|
|
1213
|
+
} finally {
|
|
1214
|
+
t.mock.timers.reset();
|
|
1215
|
+
}
|
|
1216
|
+
});
|
|
1217
|
+
|
|
1218
|
+
test("writeProfile keeps a candidate header before inline generic HTML as prose", async (t) => {
|
|
1219
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
1220
|
+
try {
|
|
1221
|
+
await withMemoryDir(async (dir) => {
|
|
1222
|
+
const storage = new StorageManager(dir);
|
|
1223
|
+
const profile = [
|
|
1224
|
+
"Preamble prose.",
|
|
1225
|
+
"",
|
|
1226
|
+
"*Last updated: literal example*",
|
|
1227
|
+
"<custom-widget>",
|
|
1228
|
+
"Custom widget content.",
|
|
1229
|
+
"</custom-widget>",
|
|
1230
|
+
"",
|
|
1231
|
+
"# Behavioral Profile",
|
|
1232
|
+
"",
|
|
1233
|
+
STALE_HEADER,
|
|
1234
|
+
"",
|
|
1235
|
+
"- Keeps the actual profile header.",
|
|
1236
|
+
"",
|
|
1237
|
+
].join("\n");
|
|
1238
|
+
|
|
1239
|
+
await storage.writeProfile(profile);
|
|
1240
|
+
|
|
1241
|
+
assert.equal(
|
|
1242
|
+
await storage.readProfile(),
|
|
1243
|
+
[
|
|
1244
|
+
"Preamble prose.",
|
|
1245
|
+
"",
|
|
1246
|
+
"*Last updated: literal example*",
|
|
1247
|
+
"<custom-widget>",
|
|
1248
|
+
"Custom widget content.",
|
|
1249
|
+
"</custom-widget>",
|
|
1250
|
+
"",
|
|
1251
|
+
"# Behavioral Profile",
|
|
1252
|
+
"",
|
|
1253
|
+
FRESH_HEADER,
|
|
1254
|
+
"",
|
|
1255
|
+
"- Keeps the actual profile header.",
|
|
1256
|
+
"",
|
|
1257
|
+
].join("\n"),
|
|
1258
|
+
);
|
|
1259
|
+
});
|
|
1260
|
+
} finally {
|
|
1261
|
+
t.mock.timers.reset();
|
|
1262
|
+
}
|
|
1263
|
+
});
|
|
1264
|
+
|
|
1265
|
+
test("writeProfile recognizes link reference definitions as metadata boundaries", async (t) => {
|
|
1266
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
1267
|
+
try {
|
|
1268
|
+
await withMemoryDir(async (dir) => {
|
|
1269
|
+
const storage = new StorageManager(dir);
|
|
1270
|
+
const profile = [
|
|
1271
|
+
"# Behavioral Profile",
|
|
1272
|
+
"",
|
|
1273
|
+
"[docs]: /url",
|
|
1274
|
+
STALE_HEADER,
|
|
1275
|
+
"",
|
|
1276
|
+
"- Keeps link-reference content.",
|
|
1277
|
+
"",
|
|
1278
|
+
].join("\n");
|
|
1279
|
+
|
|
1280
|
+
await storage.writeProfile(profile);
|
|
1281
|
+
|
|
1282
|
+
assert.equal(
|
|
1283
|
+
await storage.readProfile(),
|
|
1284
|
+
[
|
|
1285
|
+
"# Behavioral Profile",
|
|
1286
|
+
"",
|
|
1287
|
+
"[docs]: /url",
|
|
1288
|
+
FRESH_HEADER,
|
|
1289
|
+
"",
|
|
1290
|
+
"- Keeps link-reference content.",
|
|
1291
|
+
"",
|
|
1292
|
+
].join("\n"),
|
|
1293
|
+
);
|
|
1294
|
+
});
|
|
1295
|
+
} finally {
|
|
1296
|
+
t.mock.timers.reset();
|
|
1297
|
+
}
|
|
1298
|
+
});
|
|
1299
|
+
test("writeProfile ignores non-breaking spaces as link-reference separators", async (t) => {
|
|
1300
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
1301
|
+
try {
|
|
1302
|
+
await withMemoryDir(async (dir) => {
|
|
1303
|
+
const storage = new StorageManager(dir);
|
|
1304
|
+
const profile = [
|
|
1305
|
+
"# Behavioral Profile",
|
|
1306
|
+
"",
|
|
1307
|
+
"[docs]:\u00a0/url",
|
|
1308
|
+
STALE_HEADER,
|
|
1309
|
+
"",
|
|
1310
|
+
"- Keeps ordinary paragraph content.",
|
|
1311
|
+
"",
|
|
1312
|
+
].join("\n");
|
|
1313
|
+
|
|
1314
|
+
await storage.writeProfile(profile);
|
|
1315
|
+
|
|
1316
|
+
assert.equal(
|
|
1317
|
+
await storage.readProfile(),
|
|
1318
|
+
[
|
|
1319
|
+
"# Behavioral Profile",
|
|
1320
|
+
"",
|
|
1321
|
+
FRESH_HEADER,
|
|
1322
|
+
"",
|
|
1323
|
+
"[docs]:\u00a0/url",
|
|
1324
|
+
STALE_HEADER,
|
|
1325
|
+
"",
|
|
1326
|
+
"- Keeps ordinary paragraph content.",
|
|
1327
|
+
"",
|
|
1328
|
+
].join("\n"),
|
|
1329
|
+
);
|
|
1330
|
+
});
|
|
1331
|
+
} finally {
|
|
1332
|
+
t.mock.timers.reset();
|
|
1333
|
+
}
|
|
1334
|
+
});
|
|
1335
|
+
|
|
1336
|
+
test("writeProfile stops nested-list context at an intervening document paragraph", async (t) => {
|
|
1337
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
1338
|
+
try {
|
|
1339
|
+
await withMemoryDir(async (dir) => {
|
|
1340
|
+
const storage = new StorageManager(dir);
|
|
1341
|
+
const profile = [
|
|
1342
|
+
"# Behavioral Profile",
|
|
1343
|
+
"",
|
|
1344
|
+
"- item",
|
|
1345
|
+
"",
|
|
1346
|
+
"Document paragraph.",
|
|
1347
|
+
"",
|
|
1348
|
+
` ${STALE_HEADER}`,
|
|
1349
|
+
"",
|
|
1350
|
+
"- Keeps the standalone header canonical.",
|
|
1351
|
+
"",
|
|
1352
|
+
].join("\n");
|
|
1353
|
+
|
|
1354
|
+
await storage.writeProfile(profile);
|
|
1355
|
+
|
|
1356
|
+
assert.equal(
|
|
1357
|
+
await storage.readProfile(),
|
|
1358
|
+
[
|
|
1359
|
+
"# Behavioral Profile",
|
|
1360
|
+
"",
|
|
1361
|
+
"- item",
|
|
1362
|
+
"",
|
|
1363
|
+
"Document paragraph.",
|
|
1364
|
+
"",
|
|
1365
|
+
FRESH_HEADER,
|
|
1366
|
+
"",
|
|
1367
|
+
"- Keeps the standalone header canonical.",
|
|
1368
|
+
"",
|
|
1369
|
+
].join("\n"),
|
|
1370
|
+
);
|
|
1371
|
+
});
|
|
1372
|
+
} finally {
|
|
1373
|
+
t.mock.timers.reset();
|
|
1374
|
+
}
|
|
1375
|
+
});
|
|
1376
|
+
|
|
1377
|
+
test("writeProfile recognizes link-reference continuation boundaries", async (t) => {
|
|
1378
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
1379
|
+
try {
|
|
1380
|
+
await withMemoryDir(async (dir) => {
|
|
1381
|
+
const storage = new StorageManager(dir);
|
|
1382
|
+
const profile = [
|
|
1383
|
+
"# Behavioral Profile",
|
|
1384
|
+
"",
|
|
1385
|
+
"[docs]: /url",
|
|
1386
|
+
' "Docs"',
|
|
1387
|
+
STALE_HEADER,
|
|
1388
|
+
"",
|
|
1389
|
+
"- Keeps link-reference content.",
|
|
1390
|
+
"",
|
|
1391
|
+
].join("\n");
|
|
1392
|
+
|
|
1393
|
+
await storage.writeProfile(profile);
|
|
1394
|
+
|
|
1395
|
+
assert.equal(
|
|
1396
|
+
await storage.readProfile(),
|
|
1397
|
+
[
|
|
1398
|
+
"# Behavioral Profile",
|
|
1399
|
+
"",
|
|
1400
|
+
"[docs]: /url",
|
|
1401
|
+
' "Docs"',
|
|
1402
|
+
FRESH_HEADER,
|
|
1403
|
+
"",
|
|
1404
|
+
"- Keeps link-reference content.",
|
|
1405
|
+
"",
|
|
1406
|
+
].join("\n"),
|
|
1407
|
+
);
|
|
1408
|
+
});
|
|
1409
|
+
} finally {
|
|
1410
|
+
t.mock.timers.reset();
|
|
1411
|
+
}
|
|
1412
|
+
});
|
|
1413
|
+
test("writeProfile recognizes link-reference destination continuation boundaries", async (t) => {
|
|
1414
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
1415
|
+
try {
|
|
1416
|
+
await withMemoryDir(async (dir) => {
|
|
1417
|
+
const storage = new StorageManager(dir);
|
|
1418
|
+
const profile = [
|
|
1419
|
+
"# Behavioral Profile",
|
|
1420
|
+
"",
|
|
1421
|
+
"[docs]:",
|
|
1422
|
+
" /url",
|
|
1423
|
+
STALE_HEADER,
|
|
1424
|
+
"",
|
|
1425
|
+
"- Keeps link-reference content.",
|
|
1426
|
+
"",
|
|
1427
|
+
].join("\n");
|
|
1428
|
+
|
|
1429
|
+
await storage.writeProfile(profile);
|
|
1430
|
+
|
|
1431
|
+
assert.equal(
|
|
1432
|
+
await storage.readProfile(),
|
|
1433
|
+
[
|
|
1434
|
+
"# Behavioral Profile",
|
|
1435
|
+
"",
|
|
1436
|
+
"[docs]:",
|
|
1437
|
+
" /url",
|
|
1438
|
+
FRESH_HEADER,
|
|
1439
|
+
"",
|
|
1440
|
+
"- Keeps link-reference content.",
|
|
1441
|
+
"",
|
|
1442
|
+
].join("\n"),
|
|
1443
|
+
);
|
|
1444
|
+
});
|
|
1445
|
+
} finally {
|
|
1446
|
+
t.mock.timers.reset();
|
|
1447
|
+
}
|
|
1448
|
+
});
|
|
1449
|
+
test("writeProfile ignores invalid link-reference prose", async (t) => {
|
|
1450
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
1451
|
+
try {
|
|
1452
|
+
await withMemoryDir(async (dir) => {
|
|
1453
|
+
const storage = new StorageManager(dir);
|
|
1454
|
+
const profile = [
|
|
1455
|
+
"# Behavioral Profile",
|
|
1456
|
+
"",
|
|
1457
|
+
"[docs]: /url invalid title",
|
|
1458
|
+
STALE_HEADER,
|
|
1459
|
+
"",
|
|
1460
|
+
"- Keeps invalid link-reference prose.",
|
|
1461
|
+
"",
|
|
1462
|
+
].join("\n");
|
|
1463
|
+
|
|
1464
|
+
await storage.writeProfile(profile);
|
|
1465
|
+
|
|
1466
|
+
assert.equal(
|
|
1467
|
+
await storage.readProfile(),
|
|
1468
|
+
[
|
|
1469
|
+
"# Behavioral Profile",
|
|
1470
|
+
"",
|
|
1471
|
+
FRESH_HEADER,
|
|
1472
|
+
"",
|
|
1473
|
+
"[docs]: /url invalid title",
|
|
1474
|
+
STALE_HEADER,
|
|
1475
|
+
"",
|
|
1476
|
+
"- Keeps invalid link-reference prose.",
|
|
1477
|
+
"",
|
|
1478
|
+
].join("\n"),
|
|
1479
|
+
);
|
|
1480
|
+
});
|
|
1481
|
+
} finally {
|
|
1482
|
+
t.mock.timers.reset();
|
|
1483
|
+
}
|
|
1484
|
+
});
|
|
1485
|
+
test("writeProfile recognizes escaped link-reference labels", async (t) => {
|
|
1486
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
1487
|
+
try {
|
|
1488
|
+
await withMemoryDir(async (dir) => {
|
|
1489
|
+
const storage = new StorageManager(dir);
|
|
1490
|
+
const profile = [
|
|
1491
|
+
"# Behavioral Profile",
|
|
1492
|
+
"",
|
|
1493
|
+
"[foo\\]]: /url",
|
|
1494
|
+
STALE_HEADER,
|
|
1495
|
+
"",
|
|
1496
|
+
"- Keeps escaped link-reference content.",
|
|
1497
|
+
"",
|
|
1498
|
+
].join("\n");
|
|
1499
|
+
|
|
1500
|
+
await storage.writeProfile(profile);
|
|
1501
|
+
|
|
1502
|
+
assert.equal(
|
|
1503
|
+
await storage.readProfile(),
|
|
1504
|
+
[
|
|
1505
|
+
"# Behavioral Profile",
|
|
1506
|
+
"",
|
|
1507
|
+
"[foo\\]]: /url",
|
|
1508
|
+
FRESH_HEADER,
|
|
1509
|
+
"",
|
|
1510
|
+
"- Keeps escaped link-reference content.",
|
|
1511
|
+
"",
|
|
1512
|
+
].join("\n"),
|
|
1513
|
+
);
|
|
1514
|
+
});
|
|
1515
|
+
} finally {
|
|
1516
|
+
t.mock.timers.reset();
|
|
1517
|
+
}
|
|
1518
|
+
});
|
|
1519
|
+
test("writeProfile ignores malformed link-reference destinations", async (t) => {
|
|
1520
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
1521
|
+
try {
|
|
1522
|
+
await withMemoryDir(async (dir) => {
|
|
1523
|
+
const storage = new StorageManager(dir);
|
|
1524
|
+
for (const destination of ["foo(bar", "foo<bar"]) {
|
|
1525
|
+
const profile = [
|
|
1526
|
+
"# Behavioral Profile",
|
|
1527
|
+
"",
|
|
1528
|
+
`[docs]: ${destination}`,
|
|
1529
|
+
STALE_HEADER,
|
|
1530
|
+
"",
|
|
1531
|
+
"- Keeps malformed link-reference content.",
|
|
1532
|
+
"",
|
|
1533
|
+
].join("\n");
|
|
1534
|
+
|
|
1535
|
+
await storage.writeProfile(profile);
|
|
1536
|
+
|
|
1537
|
+
assert.equal(
|
|
1538
|
+
await storage.readProfile(),
|
|
1539
|
+
[
|
|
1540
|
+
"# Behavioral Profile",
|
|
1541
|
+
"",
|
|
1542
|
+
FRESH_HEADER,
|
|
1543
|
+
"",
|
|
1544
|
+
`[docs]: ${destination}`,
|
|
1545
|
+
STALE_HEADER,
|
|
1546
|
+
"",
|
|
1547
|
+
"- Keeps malformed link-reference content.",
|
|
1548
|
+
"",
|
|
1549
|
+
].join("\n"),
|
|
1550
|
+
);
|
|
1551
|
+
}
|
|
1552
|
+
});
|
|
1553
|
+
} finally {
|
|
1554
|
+
t.mock.timers.reset();
|
|
1555
|
+
}
|
|
1556
|
+
});
|
|
1557
|
+
|
|
1558
|
+
|
|
1559
|
+
test("writeProfile ignores link-reference labels with unescaped brackets", async (t) => {
|
|
1560
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
1561
|
+
try {
|
|
1562
|
+
await withMemoryDir(async (dir) => {
|
|
1563
|
+
const storage = new StorageManager(dir);
|
|
1564
|
+
const profile = [
|
|
1565
|
+
"# Behavioral Profile",
|
|
1566
|
+
"",
|
|
1567
|
+
"[foo[bar]: /url",
|
|
1568
|
+
STALE_HEADER,
|
|
1569
|
+
"",
|
|
1570
|
+
"- Keeps malformed link-reference labels.",
|
|
1571
|
+
"",
|
|
1572
|
+
].join("\n");
|
|
1573
|
+
|
|
1574
|
+
await storage.writeProfile(profile);
|
|
1575
|
+
|
|
1576
|
+
assert.equal(
|
|
1577
|
+
await storage.readProfile(),
|
|
1578
|
+
[
|
|
1579
|
+
"# Behavioral Profile",
|
|
1580
|
+
"",
|
|
1581
|
+
FRESH_HEADER,
|
|
1582
|
+
"",
|
|
1583
|
+
"[foo[bar]: /url",
|
|
1584
|
+
STALE_HEADER,
|
|
1585
|
+
"",
|
|
1586
|
+
"- Keeps malformed link-reference labels.",
|
|
1587
|
+
"",
|
|
1588
|
+
].join("\n"),
|
|
1589
|
+
);
|
|
1590
|
+
});
|
|
1591
|
+
} finally {
|
|
1592
|
+
t.mock.timers.reset();
|
|
1593
|
+
}
|
|
1594
|
+
});
|
|
1595
|
+
|
|
1596
|
+
|
|
1597
|
+
test("writeProfile ignores invalid backtick fence openers", async (t) => {
|
|
1598
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
1599
|
+
try {
|
|
1600
|
+
await withMemoryDir(async (dir) => {
|
|
1601
|
+
const storage = new StorageManager(dir);
|
|
1602
|
+
const profile = [
|
|
1603
|
+
"```markdown`invalid",
|
|
1604
|
+
"# Behavioral Profile",
|
|
1605
|
+
"",
|
|
1606
|
+
STALE_HEADER,
|
|
1607
|
+
"",
|
|
1608
|
+
"- Keeps ordinary Markdown visible.",
|
|
1609
|
+
"",
|
|
1610
|
+
].join("\n");
|
|
1611
|
+
|
|
1612
|
+
await storage.writeProfile(profile);
|
|
1613
|
+
|
|
1614
|
+
assert.equal(await storage.readProfile(), profile.replace(STALE_HEADER, FRESH_HEADER));
|
|
1615
|
+
});
|
|
1616
|
+
} finally {
|
|
1617
|
+
t.mock.timers.reset();
|
|
1618
|
+
}
|
|
1619
|
+
});
|
|
1620
|
+
|
|
1621
|
+
test("writeProfile exits an ordinary HTML block at its closing tag", async (t) => {
|
|
1622
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
1623
|
+
try {
|
|
1624
|
+
await withMemoryDir(async (dir) => {
|
|
1625
|
+
const storage = new StorageManager(dir);
|
|
1626
|
+
const profile = [
|
|
1627
|
+
"<div>",
|
|
1628
|
+
"*Last updated: literal example*",
|
|
1629
|
+
"</div>",
|
|
1630
|
+
"# Behavioral Profile",
|
|
1631
|
+
"",
|
|
1632
|
+
STALE_HEADER,
|
|
1633
|
+
"",
|
|
1634
|
+
"- Keeps metadata after HTML.",
|
|
1635
|
+
"",
|
|
1636
|
+
].join("\n");
|
|
1637
|
+
|
|
1638
|
+
await storage.writeProfile(profile);
|
|
1639
|
+
|
|
1640
|
+
assert.equal(await storage.readProfile(), profile.replace(STALE_HEADER, FRESH_HEADER));
|
|
1641
|
+
});
|
|
1642
|
+
} finally {
|
|
1643
|
+
t.mock.timers.reset();
|
|
1644
|
+
}
|
|
1645
|
+
});
|
|
1646
|
+
test("writeProfile keeps frame, frameset, and hgroup HTML blocks opaque", async (t) => {
|
|
1647
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
1648
|
+
try {
|
|
1649
|
+
await withMemoryDir(async (dir) => {
|
|
1650
|
+
const storage = new StorageManager(dir);
|
|
1651
|
+
const profile = [
|
|
1652
|
+
"Preamble prose.",
|
|
1653
|
+
"<frame>",
|
|
1654
|
+
"# Literal frame heading",
|
|
1655
|
+
STALE_HEADER,
|
|
1656
|
+
"</frame>",
|
|
1657
|
+
"<frameset>",
|
|
1658
|
+
"# Literal frameset heading",
|
|
1659
|
+
"*Last updated: literal frameset*",
|
|
1660
|
+
"</frameset>",
|
|
1661
|
+
"<hgroup>",
|
|
1662
|
+
"# Literal hgroup heading",
|
|
1663
|
+
"*Last updated: literal hgroup*",
|
|
1664
|
+
"</hgroup>",
|
|
1665
|
+
"# Behavioral Profile",
|
|
1666
|
+
"",
|
|
1667
|
+
STALE_HEADER,
|
|
1668
|
+
"",
|
|
1669
|
+
"- Keeps metadata after type-6 HTML blocks.",
|
|
1670
|
+
"",
|
|
1671
|
+
].join("\n");
|
|
1672
|
+
|
|
1673
|
+
await storage.writeProfile(profile);
|
|
1674
|
+
|
|
1675
|
+
assert.equal(
|
|
1676
|
+
await storage.readProfile(),
|
|
1677
|
+
[
|
|
1678
|
+
"Preamble prose.",
|
|
1679
|
+
"<frame>",
|
|
1680
|
+
"# Literal frame heading",
|
|
1681
|
+
STALE_HEADER,
|
|
1682
|
+
"</frame>",
|
|
1683
|
+
"<frameset>",
|
|
1684
|
+
"# Literal frameset heading",
|
|
1685
|
+
"*Last updated: literal frameset*",
|
|
1686
|
+
"</frameset>",
|
|
1687
|
+
"<hgroup>",
|
|
1688
|
+
"# Literal hgroup heading",
|
|
1689
|
+
"*Last updated: literal hgroup*",
|
|
1690
|
+
"</hgroup>",
|
|
1691
|
+
"# Behavioral Profile",
|
|
1692
|
+
"",
|
|
1693
|
+
FRESH_HEADER,
|
|
1694
|
+
"",
|
|
1695
|
+
"- Keeps metadata after type-6 HTML blocks.",
|
|
1696
|
+
"",
|
|
1697
|
+
].join("\n"),
|
|
1698
|
+
);
|
|
1699
|
+
});
|
|
1700
|
+
} finally {
|
|
1701
|
+
t.mock.timers.reset();
|
|
1702
|
+
}
|
|
1703
|
+
});
|
|
1704
|
+
test("writeProfile keeps malformed built-in HTML slashes in prose", async (t) => {
|
|
1705
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
1706
|
+
try {
|
|
1707
|
+
await withMemoryDir(async (dir) => {
|
|
1708
|
+
const storage = new StorageManager(dir);
|
|
1709
|
+
const profile = [
|
|
1710
|
+
"Preamble prose.",
|
|
1711
|
+
"",
|
|
1712
|
+
"*Last updated: literal example*",
|
|
1713
|
+
"<div/garbage",
|
|
1714
|
+
"",
|
|
1715
|
+
"# Behavioral Profile",
|
|
1716
|
+
"",
|
|
1717
|
+
STALE_HEADER,
|
|
1718
|
+
"",
|
|
1719
|
+
"- Keeps malformed HTML prose.",
|
|
1720
|
+
"",
|
|
1721
|
+
].join("\n");
|
|
1722
|
+
|
|
1723
|
+
await storage.writeProfile(profile);
|
|
1724
|
+
|
|
1725
|
+
assert.equal(
|
|
1726
|
+
await storage.readProfile(),
|
|
1727
|
+
[
|
|
1728
|
+
"Preamble prose.",
|
|
1729
|
+
"",
|
|
1730
|
+
"*Last updated: literal example*",
|
|
1731
|
+
"<div/garbage",
|
|
1732
|
+
"",
|
|
1733
|
+
"# Behavioral Profile",
|
|
1734
|
+
"",
|
|
1735
|
+
FRESH_HEADER,
|
|
1736
|
+
"",
|
|
1737
|
+
"- Keeps malformed HTML prose.",
|
|
1738
|
+
"",
|
|
1739
|
+
].join("\n"),
|
|
1740
|
+
);
|
|
1741
|
+
});
|
|
1742
|
+
} finally {
|
|
1743
|
+
t.mock.timers.reset();
|
|
1744
|
+
}
|
|
1745
|
+
});
|
|
1746
|
+
|
|
1747
|
+
|
|
1748
|
+
|
|
1749
|
+
test("writeProfile preserves HTML blocks with multiline open tags", async (t) => {
|
|
1750
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
1751
|
+
try {
|
|
1752
|
+
await withMemoryDir(async (dir) => {
|
|
1753
|
+
const storage = new StorageManager(dir);
|
|
1754
|
+
const profile = [
|
|
1755
|
+
"# Behavioral Profile",
|
|
1756
|
+
"",
|
|
1757
|
+
"<div",
|
|
1758
|
+
' class="example">',
|
|
1759
|
+
"*Last updated: literal example*",
|
|
1760
|
+
"</div>",
|
|
1761
|
+
"",
|
|
1762
|
+
"- Keeps multiline HTML.",
|
|
1763
|
+
"",
|
|
1764
|
+
].join("\n");
|
|
1765
|
+
|
|
1766
|
+
await storage.writeProfile(profile);
|
|
1767
|
+
|
|
1768
|
+
assert.equal(
|
|
1769
|
+
await storage.readProfile(),
|
|
1770
|
+
profile.replace("\n\n<div", `\n\n${FRESH_HEADER}\n\n<div`),
|
|
1771
|
+
);
|
|
1772
|
+
});
|
|
1773
|
+
} finally {
|
|
1774
|
+
t.mock.timers.reset();
|
|
1775
|
+
}
|
|
1776
|
+
});
|
|
1777
|
+
test("writeProfile keeps profile metadata inside a multiline HTML opener opaque", async (t) => {
|
|
1778
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
1779
|
+
try {
|
|
1780
|
+
await withMemoryDir(async (dir) => {
|
|
1781
|
+
const storage = new StorageManager(dir);
|
|
1782
|
+
const profile = [
|
|
1783
|
+
"<div",
|
|
1784
|
+
"# Behavioral Profile",
|
|
1785
|
+
STALE_HEADER,
|
|
1786
|
+
">",
|
|
1787
|
+
"",
|
|
1788
|
+
"- Keeps multiline opener content.",
|
|
1789
|
+
"",
|
|
1790
|
+
].join("\n");
|
|
1791
|
+
|
|
1792
|
+
await storage.writeProfile(profile);
|
|
1793
|
+
|
|
1794
|
+
assert.equal(
|
|
1795
|
+
await storage.readProfile(),
|
|
1796
|
+
[
|
|
1797
|
+
FRESH_HEADER,
|
|
1798
|
+
"",
|
|
1799
|
+
"<div",
|
|
1800
|
+
"# Behavioral Profile",
|
|
1801
|
+
STALE_HEADER,
|
|
1802
|
+
">",
|
|
1803
|
+
"",
|
|
1804
|
+
"- Keeps multiline opener content.",
|
|
1805
|
+
"",
|
|
1806
|
+
].join("\n"),
|
|
1807
|
+
);
|
|
1808
|
+
});
|
|
1809
|
+
} finally {
|
|
1810
|
+
t.mock.timers.reset();
|
|
1811
|
+
}
|
|
1812
|
+
});
|
|
1813
|
+
|
|
1814
|
+
test("writeProfile closes an active HTML block before skipping indented code", async (t) => {
|
|
1815
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
1816
|
+
try {
|
|
1817
|
+
await withMemoryDir(async (dir) => {
|
|
1818
|
+
const storage = new StorageManager(dir);
|
|
1819
|
+
const profile = [
|
|
1820
|
+
"<pre>",
|
|
1821
|
+
"*Last updated: literal example*",
|
|
1822
|
+
" </pre>",
|
|
1823
|
+
"# Behavioral Profile",
|
|
1824
|
+
"",
|
|
1825
|
+
STALE_HEADER,
|
|
1826
|
+
"",
|
|
1827
|
+
"- Keeps metadata after HTML.",
|
|
1828
|
+
"",
|
|
1829
|
+
].join("\n");
|
|
1830
|
+
|
|
1831
|
+
await storage.writeProfile(profile);
|
|
1832
|
+
|
|
1833
|
+
assert.equal(await storage.readProfile(), profile.replace(STALE_HEADER, FRESH_HEADER));
|
|
1834
|
+
});
|
|
1835
|
+
} finally {
|
|
1836
|
+
t.mock.timers.reset();
|
|
1837
|
+
}
|
|
1838
|
+
});
|
|
1839
|
+
|
|
1840
|
+
|
|
1841
|
+
test("writeProfile preserves timestamp-shaped generic HTML blocks", async (t) => {
|
|
1842
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
1843
|
+
try {
|
|
1844
|
+
await withMemoryDir(async (dir) => {
|
|
1845
|
+
const storage = new StorageManager(dir);
|
|
1846
|
+
const profile = [
|
|
1847
|
+
"# Behavioral Profile",
|
|
1848
|
+
"",
|
|
1849
|
+
"<custom-widget>",
|
|
1850
|
+
"*Last updated: literal example*",
|
|
1851
|
+
"</custom-widget>",
|
|
1852
|
+
"",
|
|
1853
|
+
"- Keeps custom HTML.",
|
|
1854
|
+
"",
|
|
1855
|
+
].join("\n");
|
|
1856
|
+
|
|
1857
|
+
await storage.writeProfile(profile);
|
|
1858
|
+
|
|
1859
|
+
assert.equal(
|
|
1860
|
+
await storage.readProfile(),
|
|
1861
|
+
profile.replace("\n\n<custom-widget>", `\n\n${FRESH_HEADER}\n\n<custom-widget>`),
|
|
1862
|
+
);
|
|
1863
|
+
});
|
|
1864
|
+
} finally {
|
|
1865
|
+
t.mock.timers.reset();
|
|
1866
|
+
}
|
|
1867
|
+
});
|
|
1868
|
+
|
|
1869
|
+
test("writeProfile keeps built-in closing blocks opaque after paragraph prose", async (t) => {
|
|
1870
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
1871
|
+
try {
|
|
1872
|
+
await withMemoryDir(async (dir) => {
|
|
1873
|
+
const storage = new StorageManager(dir);
|
|
1874
|
+
const profile = [
|
|
1875
|
+
"Preamble prose.",
|
|
1876
|
+
"</div>",
|
|
1877
|
+
"# Literal heading",
|
|
1878
|
+
"*Last updated: literal example*",
|
|
1879
|
+
"",
|
|
1880
|
+
"# Behavioral Profile",
|
|
1881
|
+
"",
|
|
1882
|
+
STALE_HEADER,
|
|
1883
|
+
"",
|
|
1884
|
+
"- Keeps the real profile metadata.",
|
|
1885
|
+
"",
|
|
1886
|
+
].join("\n");
|
|
1887
|
+
|
|
1888
|
+
await storage.writeProfile(profile);
|
|
1889
|
+
|
|
1890
|
+
assert.equal(await storage.readProfile(), profile.replace(STALE_HEADER, FRESH_HEADER));
|
|
1891
|
+
});
|
|
1892
|
+
} finally {
|
|
1893
|
+
t.mock.timers.reset();
|
|
1894
|
+
}
|
|
1895
|
+
});
|
|
1896
|
+
test("writeProfile keeps generic HTML inline after paragraph prose", async (t) => {
|
|
1897
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
1898
|
+
try {
|
|
1899
|
+
await withMemoryDir(async (dir) => {
|
|
1900
|
+
const storage = new StorageManager(dir);
|
|
1901
|
+
const profile = [
|
|
1902
|
+
"Preamble prose.",
|
|
1903
|
+
"<custom-widget>",
|
|
1904
|
+
"# Behavioral Profile",
|
|
1905
|
+
STALE_HEADER,
|
|
1906
|
+
"",
|
|
1907
|
+
"Profile body.",
|
|
1908
|
+
"",
|
|
1909
|
+
].join("\n");
|
|
1910
|
+
|
|
1911
|
+
await storage.writeProfile(profile);
|
|
1912
|
+
|
|
1913
|
+
assert.equal(await storage.readProfile(), profile.replace(STALE_HEADER, FRESH_HEADER));
|
|
1914
|
+
});
|
|
1915
|
+
} finally {
|
|
1916
|
+
t.mock.timers.reset();
|
|
1917
|
+
}
|
|
1918
|
+
});
|
|
1919
|
+
test("writeProfile keeps lazy list timestamp text as content", async (t) => {
|
|
1920
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
1921
|
+
try {
|
|
1922
|
+
await withMemoryDir(async (dir) => {
|
|
1923
|
+
const storage = new StorageManager(dir);
|
|
1924
|
+
const profile = [
|
|
1925
|
+
"- Explanation in a list item.",
|
|
1926
|
+
"*Last updated: literal example*",
|
|
1927
|
+
"",
|
|
1928
|
+
"Body prose.",
|
|
1929
|
+
"",
|
|
1930
|
+
].join("\n");
|
|
1931
|
+
|
|
1932
|
+
await storage.writeProfile(profile);
|
|
1933
|
+
|
|
1934
|
+
assert.equal(await storage.readProfile(), `${FRESH_HEADER}\n\n${profile}`);
|
|
1935
|
+
});
|
|
1936
|
+
} finally {
|
|
1937
|
+
t.mock.timers.reset();
|
|
1938
|
+
}
|
|
1939
|
+
});
|
|
1940
|
+
test("writeProfile keeps inline HTML blocks opaque until a blank line", async (t) => {
|
|
1941
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
1942
|
+
try {
|
|
1943
|
+
await withMemoryDir(async (dir) => {
|
|
1944
|
+
const storage = new StorageManager(dir);
|
|
1945
|
+
const profile = [
|
|
1946
|
+
"# Behavioral Profile",
|
|
1947
|
+
"",
|
|
1948
|
+
"<div>example</div>",
|
|
1949
|
+
STALE_HEADER,
|
|
1950
|
+
"",
|
|
1951
|
+
"- Keeps inline HTML opaque.",
|
|
1952
|
+
"",
|
|
1953
|
+
].join("\n");
|
|
1954
|
+
|
|
1955
|
+
await storage.writeProfile(profile);
|
|
1956
|
+
|
|
1957
|
+
assert.equal(
|
|
1958
|
+
await storage.readProfile(),
|
|
1959
|
+
profile.replace("\n\n<div>example</div>", `\n\n${FRESH_HEADER}\n\n<div>example</div>`),
|
|
1960
|
+
);
|
|
1961
|
+
});
|
|
1962
|
+
} finally {
|
|
1963
|
+
t.mock.timers.reset();
|
|
1964
|
+
}
|
|
1965
|
+
});
|
|
1966
|
+
|
|
1967
|
+
test("writeProfile keeps the post-title header when duplicate headers exist", async (t) => {
|
|
1968
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
1969
|
+
try {
|
|
1970
|
+
await withMemoryDir(async (dir) => {
|
|
1971
|
+
const storage = new StorageManager(dir);
|
|
1972
|
+
const preTitleHeader = "*Last updated: 2023-01-02T03:04:05.000Z*";
|
|
1973
|
+
const profile = [
|
|
1974
|
+
preTitleHeader,
|
|
1975
|
+
"",
|
|
1976
|
+
"# Behavioral Profile",
|
|
1977
|
+
"",
|
|
1978
|
+
STALE_HEADER,
|
|
1979
|
+
"",
|
|
1980
|
+
"- Keeps the canonical header after the title.",
|
|
1981
|
+
"",
|
|
1982
|
+
].join("\n");
|
|
1983
|
+
|
|
1984
|
+
await storage.writeProfile(profile);
|
|
1985
|
+
|
|
1986
|
+
const updated = await storage.readProfile();
|
|
1987
|
+
assert.equal((updated.match(/^\*Last updated: .*$/gm) ?? []).length, 1);
|
|
1988
|
+
assert.equal(updated.includes(preTitleHeader), false);
|
|
1989
|
+
assert.ok(updated.includes(`# Behavioral Profile\n\n${FRESH_HEADER}`));
|
|
1990
|
+
});
|
|
1991
|
+
} finally {
|
|
1992
|
+
t.mock.timers.reset();
|
|
1993
|
+
}
|
|
1994
|
+
});
|
|
1995
|
+
test("writeProfile retains a BOM when removing a duplicate pre-title header", async (t) => {
|
|
1996
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
1997
|
+
try {
|
|
1998
|
+
await withMemoryDir(async (dir) => {
|
|
1999
|
+
const storage = new StorageManager(dir);
|
|
2000
|
+
const profile = [
|
|
2001
|
+
`\uFEFF${STALE_HEADER}`,
|
|
2002
|
+
"# Behavioral Profile",
|
|
2003
|
+
"",
|
|
2004
|
+
STALE_HEADER,
|
|
2005
|
+
"",
|
|
2006
|
+
"- Keeps the file marker.",
|
|
2007
|
+
"",
|
|
2008
|
+
].join("\n");
|
|
2009
|
+
|
|
2010
|
+
await storage.writeProfile(profile);
|
|
2011
|
+
|
|
2012
|
+
assert.equal(
|
|
2013
|
+
await storage.readProfile(),
|
|
2014
|
+
[
|
|
2015
|
+
"\uFEFF# Behavioral Profile",
|
|
2016
|
+
"",
|
|
2017
|
+
FRESH_HEADER,
|
|
2018
|
+
"",
|
|
2019
|
+
"- Keeps the file marker.",
|
|
2020
|
+
"",
|
|
2021
|
+
].join("\n"),
|
|
2022
|
+
);
|
|
2023
|
+
});
|
|
2024
|
+
} finally {
|
|
2025
|
+
t.mock.timers.reset();
|
|
2026
|
+
}
|
|
2027
|
+
});
|
|
2028
|
+
|
|
2029
|
+
test("writeProfile recognizes heading and list metadata boundaries", async (t) => {
|
|
2030
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
2031
|
+
try {
|
|
2032
|
+
await withMemoryDir(async (dir) => {
|
|
2033
|
+
const storage = new StorageManager(dir);
|
|
2034
|
+
const profiles = [
|
|
2035
|
+
[
|
|
2036
|
+
"# Behavioral Profile",
|
|
2037
|
+
"",
|
|
2038
|
+
"## Notes",
|
|
2039
|
+
STALE_HEADER,
|
|
2040
|
+
"",
|
|
2041
|
+
"- Keeps heading metadata.",
|
|
2042
|
+
"",
|
|
2043
|
+
].join("\n"),
|
|
2044
|
+
[
|
|
2045
|
+
"# Behavioral Profile",
|
|
2046
|
+
"",
|
|
2047
|
+
"- Existing note.",
|
|
2048
|
+
"",
|
|
2049
|
+
STALE_HEADER,
|
|
2050
|
+
"",
|
|
2051
|
+
"- Keeps list metadata.",
|
|
2052
|
+
"",
|
|
2053
|
+
].join("\n"),
|
|
2054
|
+
];
|
|
2055
|
+
|
|
2056
|
+
for (const profile of profiles) {
|
|
2057
|
+
await storage.writeProfile(profile);
|
|
2058
|
+
assert.equal(await storage.readProfile(), profile.replace(STALE_HEADER, FRESH_HEADER));
|
|
2059
|
+
}
|
|
2060
|
+
});
|
|
2061
|
+
} finally {
|
|
2062
|
+
t.mock.timers.reset();
|
|
2063
|
+
}
|
|
2064
|
+
});
|
|
2065
|
+
|
|
2066
|
+
test("writeProfile recognizes raw HTML terminators as metadata boundaries", async (t) => {
|
|
2067
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
2068
|
+
try {
|
|
2069
|
+
await withMemoryDir(async (dir) => {
|
|
2070
|
+
const storage = new StorageManager(dir);
|
|
2071
|
+
const profile = [
|
|
2072
|
+
"# Behavioral Profile",
|
|
2073
|
+
"",
|
|
2074
|
+
"<pre>",
|
|
2075
|
+
"Literal preformatted content.",
|
|
2076
|
+
"</pre>",
|
|
2077
|
+
STALE_HEADER,
|
|
2078
|
+
"",
|
|
2079
|
+
"- Keeps metadata after raw HTML.",
|
|
2080
|
+
"",
|
|
2081
|
+
].join("\n");
|
|
2082
|
+
|
|
2083
|
+
await storage.writeProfile(profile);
|
|
2084
|
+
|
|
2085
|
+
assert.equal(await storage.readProfile(), profile.replace(STALE_HEADER, FRESH_HEADER));
|
|
2086
|
+
});
|
|
2087
|
+
} finally {
|
|
2088
|
+
t.mock.timers.reset();
|
|
2089
|
+
}
|
|
2090
|
+
});
|
|
2091
|
+
|
|
2092
|
+
test("writeProfile recognizes comment, instruction, and CDATA terminators as boundaries", async (t) => {
|
|
2093
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
2094
|
+
try {
|
|
2095
|
+
await withMemoryDir(async (dir) => {
|
|
2096
|
+
const storage = new StorageManager(dir);
|
|
2097
|
+
const blocks = [
|
|
2098
|
+
["<!--", "Comment content.", "-->"],
|
|
2099
|
+
["<?instruction", "Instruction content.", "?>"],
|
|
2100
|
+
["<![CDATA[", "CDATA content.", "]]>"],
|
|
2101
|
+
];
|
|
2102
|
+
|
|
2103
|
+
for (const [opening, body, closing] of blocks) {
|
|
2104
|
+
const profile = [
|
|
2105
|
+
"# Behavioral Profile",
|
|
2106
|
+
"",
|
|
2107
|
+
opening,
|
|
2108
|
+
body,
|
|
2109
|
+
closing,
|
|
2110
|
+
STALE_HEADER,
|
|
2111
|
+
"",
|
|
2112
|
+
"- Keeps metadata after HTML blocks.",
|
|
2113
|
+
"",
|
|
2114
|
+
].join("\n");
|
|
2115
|
+
|
|
2116
|
+
await storage.writeProfile(profile);
|
|
2117
|
+
|
|
2118
|
+
assert.equal(await storage.readProfile(), profile.replace(STALE_HEADER, FRESH_HEADER));
|
|
2119
|
+
}
|
|
2120
|
+
});
|
|
2121
|
+
} finally {
|
|
2122
|
+
t.mock.timers.reset();
|
|
2123
|
+
}
|
|
2124
|
+
});
|
|
2125
|
+
test("writeProfile preserves prose terminators outside HTML blocks", async (t) => {
|
|
2126
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
2127
|
+
try {
|
|
2128
|
+
await withMemoryDir(async (dir) => {
|
|
2129
|
+
const storage = new StorageManager(dir);
|
|
2130
|
+
const proseHeader = "*Last updated: literal example*";
|
|
2131
|
+
const profile = [
|
|
2132
|
+
"# Behavioral Profile",
|
|
2133
|
+
"",
|
|
2134
|
+
"This example mentions -->",
|
|
2135
|
+
proseHeader,
|
|
2136
|
+
"Plain prose remains content.",
|
|
2137
|
+
"",
|
|
2138
|
+
].join("\n");
|
|
2139
|
+
|
|
2140
|
+
await storage.writeProfile(profile);
|
|
2141
|
+
|
|
2142
|
+
assert.equal(
|
|
2143
|
+
await storage.readProfile(),
|
|
2144
|
+
[
|
|
2145
|
+
"# Behavioral Profile",
|
|
2146
|
+
"",
|
|
2147
|
+
FRESH_HEADER,
|
|
2148
|
+
"",
|
|
2149
|
+
"This example mentions -->",
|
|
2150
|
+
proseHeader,
|
|
2151
|
+
"Plain prose remains content.",
|
|
2152
|
+
"",
|
|
2153
|
+
].join("\n"),
|
|
2154
|
+
);
|
|
2155
|
+
});
|
|
2156
|
+
} finally {
|
|
2157
|
+
t.mock.timers.reset();
|
|
2158
|
+
}
|
|
2159
|
+
});
|
|
2160
|
+
test("writeProfile removes duplicate stale headers", async (t) => {
|
|
2161
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
2162
|
+
try {
|
|
2163
|
+
await withMemoryDir(async (dir) => {
|
|
2164
|
+
const storage = new StorageManager(dir);
|
|
2165
|
+
await storage.writeProfile([
|
|
2166
|
+
"# Behavioral Profile",
|
|
2167
|
+
"",
|
|
2168
|
+
STALE_HEADER,
|
|
2169
|
+
"",
|
|
2170
|
+
"*Last updated: 2023-01-02T03:04:05.000Z*",
|
|
2171
|
+
"",
|
|
2172
|
+
"- Keeps decisions short.",
|
|
2173
|
+
"",
|
|
2174
|
+
].join("\n"));
|
|
2175
|
+
|
|
2176
|
+
const profile = await storage.readProfile();
|
|
2177
|
+
assert.deepEqual(profile.match(/^\*Last updated:.*\*$/gm), [FRESH_HEADER]);
|
|
2178
|
+
assert.match(profile, /- Keeps decisions short\./);
|
|
2179
|
+
});
|
|
2180
|
+
} finally {
|
|
2181
|
+
t.mock.timers.reset();
|
|
2182
|
+
}
|
|
2183
|
+
});
|
|
2184
|
+
|
|
2185
|
+
test("writeProfile replaces a noncanonical metadata header", async (t) => {
|
|
2186
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
2187
|
+
try {
|
|
2188
|
+
await withMemoryDir(async (dir) => {
|
|
2189
|
+
const storage = new StorageManager(dir);
|
|
2190
|
+
const profile = [
|
|
2191
|
+
"# Behavioral Profile",
|
|
2192
|
+
"",
|
|
2193
|
+
"*Last updated: before launch*",
|
|
2194
|
+
"",
|
|
2195
|
+
"- Keeps decisions short.",
|
|
2196
|
+
"",
|
|
2197
|
+
].join("\n");
|
|
2198
|
+
|
|
2199
|
+
await storage.writeProfile(profile);
|
|
2200
|
+
|
|
2201
|
+
assert.equal(
|
|
2202
|
+
await storage.readProfile(),
|
|
2203
|
+
profile.replace("*Last updated: before launch*", FRESH_HEADER),
|
|
2204
|
+
);
|
|
2205
|
+
});
|
|
2206
|
+
} finally {
|
|
2207
|
+
t.mock.timers.reset();
|
|
2208
|
+
}
|
|
2209
|
+
});
|
|
2210
|
+
|
|
2211
|
+
test("writeProfile preserves timestamp prose before a noninterrupting ordered list", async (t) => {
|
|
2212
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
2213
|
+
try {
|
|
2214
|
+
await withMemoryDir(async (dir) => {
|
|
2215
|
+
const storage = new StorageManager(dir);
|
|
2216
|
+
const profile = [
|
|
2217
|
+
"# Behavioral Profile",
|
|
2218
|
+
"",
|
|
2219
|
+
"*Last updated: literal example*",
|
|
2220
|
+
"2. continuation",
|
|
2221
|
+
"",
|
|
2222
|
+
"- Keeps prose content.",
|
|
2223
|
+
"",
|
|
2224
|
+
].join("\n");
|
|
2225
|
+
|
|
2226
|
+
await storage.writeProfile(profile);
|
|
2227
|
+
|
|
2228
|
+
assert.equal(
|
|
2229
|
+
await storage.readProfile(),
|
|
2230
|
+
[
|
|
2231
|
+
"# Behavioral Profile",
|
|
2232
|
+
"",
|
|
2233
|
+
FRESH_HEADER,
|
|
2234
|
+
"",
|
|
2235
|
+
"*Last updated: literal example*",
|
|
2236
|
+
"2. continuation",
|
|
2237
|
+
"",
|
|
2238
|
+
"- Keeps prose content.",
|
|
2239
|
+
"",
|
|
2240
|
+
].join("\n"),
|
|
2241
|
+
);
|
|
2242
|
+
});
|
|
2243
|
+
} finally {
|
|
2244
|
+
t.mock.timers.reset();
|
|
2245
|
+
}
|
|
2246
|
+
});
|
|
2247
|
+
|
|
2248
|
+
test("writeProfile preserves timestamp prose before indented code", async (t) => {
|
|
2249
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
2250
|
+
try {
|
|
2251
|
+
await withMemoryDir(async (dir) => {
|
|
2252
|
+
const storage = new StorageManager(dir);
|
|
2253
|
+
const profile = [
|
|
2254
|
+
"# Behavioral Profile",
|
|
2255
|
+
"",
|
|
2256
|
+
"*Last updated: literal example*",
|
|
2257
|
+
" # code example",
|
|
2258
|
+
"",
|
|
2259
|
+
"- Keeps prose content.",
|
|
2260
|
+
"",
|
|
2261
|
+
].join("\n");
|
|
2262
|
+
|
|
2263
|
+
await storage.writeProfile(profile);
|
|
2264
|
+
|
|
2265
|
+
assert.equal(
|
|
2266
|
+
await storage.readProfile(),
|
|
2267
|
+
[
|
|
2268
|
+
"# Behavioral Profile",
|
|
2269
|
+
"",
|
|
2270
|
+
FRESH_HEADER,
|
|
2271
|
+
"",
|
|
2272
|
+
"*Last updated: literal example*",
|
|
2273
|
+
" # code example",
|
|
2274
|
+
"",
|
|
2275
|
+
"- Keeps prose content.",
|
|
2276
|
+
"",
|
|
2277
|
+
].join("\n"),
|
|
2278
|
+
);
|
|
2279
|
+
});
|
|
2280
|
+
} finally {
|
|
2281
|
+
t.mock.timers.reset();
|
|
2282
|
+
}
|
|
2283
|
+
});
|
|
2284
|
+
|
|
2285
|
+
test("writeProfile recognizes one-to-three-space headings after metadata", async (t) => {
|
|
2286
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
2287
|
+
try {
|
|
2288
|
+
await withMemoryDir(async (dir) => {
|
|
2289
|
+
const storage = new StorageManager(dir);
|
|
2290
|
+
const profile = [
|
|
2291
|
+
"# Behavioral Profile",
|
|
2292
|
+
"",
|
|
2293
|
+
STALE_HEADER,
|
|
2294
|
+
" ## Section",
|
|
2295
|
+
"",
|
|
2296
|
+
"- Keeps heading content.",
|
|
2297
|
+
"",
|
|
2298
|
+
].join("\n");
|
|
2299
|
+
|
|
2300
|
+
await storage.writeProfile(profile);
|
|
2301
|
+
|
|
2302
|
+
assert.equal(await storage.readProfile(), profile.replace(STALE_HEADER, FRESH_HEADER));
|
|
2303
|
+
});
|
|
2304
|
+
} finally {
|
|
2305
|
+
t.mock.timers.reset();
|
|
2306
|
+
}
|
|
2307
|
+
});
|
|
2308
|
+
|
|
2309
|
+
test("writeProfile preserves timestamp prose after indented paragraph content", async (t) => {
|
|
2310
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
2311
|
+
try {
|
|
2312
|
+
await withMemoryDir(async (dir) => {
|
|
2313
|
+
const storage = new StorageManager(dir);
|
|
2314
|
+
const profile = [
|
|
2315
|
+
"# Behavioral Profile",
|
|
2316
|
+
"",
|
|
2317
|
+
"Plain prose",
|
|
2318
|
+
" # code example",
|
|
2319
|
+
STALE_HEADER,
|
|
2320
|
+
"",
|
|
2321
|
+
"- Keeps paragraph content.",
|
|
2322
|
+
"",
|
|
2323
|
+
].join("\n");
|
|
2324
|
+
|
|
2325
|
+
await storage.writeProfile(profile);
|
|
2326
|
+
|
|
2327
|
+
assert.equal(
|
|
2328
|
+
await storage.readProfile(),
|
|
2329
|
+
[
|
|
2330
|
+
"# Behavioral Profile",
|
|
2331
|
+
"",
|
|
2332
|
+
FRESH_HEADER,
|
|
2333
|
+
"",
|
|
2334
|
+
"Plain prose",
|
|
2335
|
+
" # code example",
|
|
2336
|
+
STALE_HEADER,
|
|
2337
|
+
"",
|
|
2338
|
+
"- Keeps paragraph content.",
|
|
2339
|
+
"",
|
|
2340
|
+
].join("\n"),
|
|
2341
|
+
);
|
|
2342
|
+
});
|
|
2343
|
+
} finally {
|
|
2344
|
+
t.mock.timers.reset();
|
|
2345
|
+
}
|
|
2346
|
+
});
|
|
2347
|
+
test("writeProfile preserves timestamp-shaped prose continuations", async (t) => {
|
|
2348
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
2349
|
+
try {
|
|
2350
|
+
await withMemoryDir(async (dir) => {
|
|
2351
|
+
const storage = new StorageManager(dir);
|
|
2352
|
+
const profile = [
|
|
2353
|
+
"# Behavioral Profile",
|
|
2354
|
+
"",
|
|
2355
|
+
"This example displays the following label",
|
|
2356
|
+
"*Last updated: literal example*",
|
|
2357
|
+
"",
|
|
2358
|
+
"- Keeps prose content.",
|
|
2359
|
+
"",
|
|
2360
|
+
].join("\n");
|
|
2361
|
+
|
|
2362
|
+
await storage.writeProfile(profile);
|
|
2363
|
+
|
|
2364
|
+
assert.equal(
|
|
2365
|
+
await storage.readProfile(),
|
|
2366
|
+
profile.replace(
|
|
2367
|
+
"\n\nThis example displays",
|
|
2368
|
+
`\n\n${FRESH_HEADER}\n\nThis example displays`,
|
|
2369
|
+
),
|
|
2370
|
+
);
|
|
2371
|
+
});
|
|
2372
|
+
} finally {
|
|
2373
|
+
t.mock.timers.reset();
|
|
2374
|
+
}
|
|
2375
|
+
});
|
|
2376
|
+
test("writeProfile refreshes compact title metadata", async (t) => {
|
|
2377
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
2378
|
+
try {
|
|
2379
|
+
await withMemoryDir(async (dir) => {
|
|
2380
|
+
const storage = new StorageManager(dir);
|
|
2381
|
+
const profile = [
|
|
2382
|
+
"# Behavioral Profile",
|
|
2383
|
+
STALE_HEADER,
|
|
2384
|
+
"*Last updated: 2023-01-02T03:04:05.000Z*",
|
|
2385
|
+
"",
|
|
2386
|
+
"- Keeps compact metadata.",
|
|
2387
|
+
"",
|
|
2388
|
+
].join("\n");
|
|
2389
|
+
|
|
2390
|
+
await storage.writeProfile(profile);
|
|
2391
|
+
|
|
2392
|
+
assert.equal(
|
|
2393
|
+
await storage.readProfile(),
|
|
2394
|
+
`# Behavioral Profile\n${FRESH_HEADER}\n\n- Keeps compact metadata.\n`,
|
|
2395
|
+
);
|
|
2396
|
+
});
|
|
2397
|
+
} finally {
|
|
2398
|
+
t.mock.timers.reset();
|
|
2399
|
+
}
|
|
2400
|
+
});
|
|
2401
|
+
test("writeProfile recognizes heading and fence metadata boundaries", async (t) => {
|
|
2402
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
2403
|
+
try {
|
|
2404
|
+
await withMemoryDir(async (dir) => {
|
|
2405
|
+
const storage = new StorageManager(dir);
|
|
2406
|
+
const profiles = [
|
|
2407
|
+
[STALE_HEADER, "# Behavioral Profile", "", "- Keeps leading metadata.", ""].join("\n"),
|
|
2408
|
+
[
|
|
2409
|
+
"# Behavioral Profile",
|
|
2410
|
+
"",
|
|
2411
|
+
"```",
|
|
2412
|
+
"- Keeps fenced content.",
|
|
2413
|
+
"```",
|
|
2414
|
+
STALE_HEADER,
|
|
2415
|
+
"",
|
|
2416
|
+
"- Keeps trailing metadata.",
|
|
2417
|
+
"",
|
|
2418
|
+
].join("\n"),
|
|
2419
|
+
];
|
|
2420
|
+
|
|
2421
|
+
for (const profile of profiles) {
|
|
2422
|
+
await storage.writeProfile(profile);
|
|
2423
|
+
assert.equal(await storage.readProfile(), profile.replace(STALE_HEADER, FRESH_HEADER));
|
|
2424
|
+
}
|
|
2425
|
+
});
|
|
2426
|
+
} finally {
|
|
2427
|
+
t.mock.timers.reset();
|
|
2428
|
+
}
|
|
2429
|
+
});
|
|
2430
|
+
test("writeProfile closes list-contained fences when the list ends", async (t) => {
|
|
2431
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
2432
|
+
try {
|
|
2433
|
+
await withMemoryDir(async (dir) => {
|
|
2434
|
+
const storage = new StorageManager(dir);
|
|
2435
|
+
const profile = [
|
|
2436
|
+
"# Behavioral Profile",
|
|
2437
|
+
"",
|
|
2438
|
+
"- item",
|
|
2439
|
+
" ```",
|
|
2440
|
+
" code",
|
|
2441
|
+
STALE_HEADER,
|
|
2442
|
+
"",
|
|
2443
|
+
"- Keeps metadata after a list fence.",
|
|
2444
|
+
"",
|
|
2445
|
+
].join("\n");
|
|
2446
|
+
|
|
2447
|
+
await storage.writeProfile(profile);
|
|
2448
|
+
|
|
2449
|
+
assert.equal(await storage.readProfile(), profile.replace(STALE_HEADER, FRESH_HEADER));
|
|
2450
|
+
});
|
|
2451
|
+
} finally {
|
|
2452
|
+
t.mock.timers.reset();
|
|
2453
|
+
}
|
|
2454
|
+
});
|
|
2455
|
+
|
|
2456
|
+
test("writeProfile preserves consecutive timestamp-shaped prose", async (t) => {
|
|
2457
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
2458
|
+
try {
|
|
2459
|
+
await withMemoryDir(async (dir) => {
|
|
2460
|
+
const storage = new StorageManager(dir);
|
|
2461
|
+
const profile = [
|
|
2462
|
+
"# Behavioral Profile",
|
|
2463
|
+
"",
|
|
2464
|
+
"Plain prose.",
|
|
2465
|
+
"*Last updated: literal one*",
|
|
2466
|
+
"*Last updated: literal two*",
|
|
2467
|
+
"",
|
|
2468
|
+
"- Keeps prose.",
|
|
2469
|
+
"",
|
|
2470
|
+
].join("\n");
|
|
2471
|
+
|
|
2472
|
+
await storage.writeProfile(profile);
|
|
2473
|
+
|
|
2474
|
+
assert.equal(
|
|
2475
|
+
await storage.readProfile(),
|
|
2476
|
+
profile.replace("\n\nPlain prose.", `\n\n${FRESH_HEADER}\n\nPlain prose.`),
|
|
2477
|
+
);
|
|
2478
|
+
});
|
|
2479
|
+
} finally {
|
|
2480
|
+
t.mock.timers.reset();
|
|
2481
|
+
}
|
|
2482
|
+
});
|
|
2483
|
+
|
|
2484
|
+
test("writeProfile preserves timestamp prose before a heading with a nonbreaking space", async (t) => {
|
|
2485
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
2486
|
+
try {
|
|
2487
|
+
await withMemoryDir(async (dir) => {
|
|
2488
|
+
const storage = new StorageManager(dir);
|
|
2489
|
+
const profile = [
|
|
2490
|
+
"# Behavioral Profile",
|
|
2491
|
+
"",
|
|
2492
|
+
"*Last updated: literal*",
|
|
2493
|
+
"#\u00a0continued",
|
|
2494
|
+
"",
|
|
2495
|
+
"- Keeps heading prose.",
|
|
2496
|
+
"",
|
|
2497
|
+
].join("\n");
|
|
2498
|
+
|
|
2499
|
+
await storage.writeProfile(profile);
|
|
2500
|
+
|
|
2501
|
+
assert.equal(
|
|
2502
|
+
await storage.readProfile(),
|
|
2503
|
+
profile.replace(
|
|
2504
|
+
"\n\n*Last updated: literal*",
|
|
2505
|
+
`\n\n${FRESH_HEADER}\n\n*Last updated: literal*`,
|
|
2506
|
+
),
|
|
2507
|
+
);
|
|
2508
|
+
});
|
|
2509
|
+
} finally {
|
|
2510
|
+
t.mock.timers.reset();
|
|
2511
|
+
}
|
|
2512
|
+
});
|
|
2513
|
+
test("writeProfile closes list-contained HTML blocks when the list ends", async (t) => {
|
|
2514
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
2515
|
+
try {
|
|
2516
|
+
await withMemoryDir(async (dir) => {
|
|
2517
|
+
const storage = new StorageManager(dir);
|
|
2518
|
+
const profile = [
|
|
2519
|
+
"# Behavioral Profile",
|
|
2520
|
+
"",
|
|
2521
|
+
"- item",
|
|
2522
|
+
" <pre>",
|
|
2523
|
+
" code",
|
|
2524
|
+
STALE_HEADER,
|
|
2525
|
+
"",
|
|
2526
|
+
"- Keeps metadata after list HTML.",
|
|
2527
|
+
"",
|
|
2528
|
+
].join("\n");
|
|
2529
|
+
|
|
2530
|
+
await storage.writeProfile(profile);
|
|
2531
|
+
|
|
2532
|
+
assert.equal(await storage.readProfile(), profile.replace(STALE_HEADER, FRESH_HEADER));
|
|
2533
|
+
});
|
|
2534
|
+
} finally {
|
|
2535
|
+
t.mock.timers.reset();
|
|
2536
|
+
}
|
|
2537
|
+
});
|
|
2538
|
+
|
|
2539
|
+
test("writeProfile preserves timestamp prose before a list marker with a nonbreaking space", async (t) => {
|
|
2540
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
2541
|
+
try {
|
|
2542
|
+
await withMemoryDir(async (dir) => {
|
|
2543
|
+
const storage = new StorageManager(dir);
|
|
2544
|
+
const profile = [
|
|
2545
|
+
"# Behavioral Profile",
|
|
2546
|
+
"",
|
|
2547
|
+
"*Last updated: literal*",
|
|
2548
|
+
"-\u00a0continuation",
|
|
2549
|
+
"",
|
|
2550
|
+
"- Keeps list prose.",
|
|
2551
|
+
"",
|
|
2552
|
+
].join("\n");
|
|
2553
|
+
|
|
2554
|
+
await storage.writeProfile(profile);
|
|
2555
|
+
|
|
2556
|
+
assert.equal(
|
|
2557
|
+
await storage.readProfile(),
|
|
2558
|
+
profile.replace(
|
|
2559
|
+
"\n\n*Last updated: literal*",
|
|
2560
|
+
`\n\n${FRESH_HEADER}\n\n*Last updated: literal*`,
|
|
2561
|
+
),
|
|
2562
|
+
);
|
|
2563
|
+
});
|
|
2564
|
+
} finally {
|
|
2565
|
+
t.mock.timers.reset();
|
|
2566
|
+
}
|
|
2567
|
+
});
|
|
2568
|
+
|
|
2569
|
+
test("writeProfile recognizes whitespace-only metadata gaps", async (t) => {
|
|
2570
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
2571
|
+
try {
|
|
2572
|
+
await withMemoryDir(async (dir) => {
|
|
2573
|
+
const storage = new StorageManager(dir);
|
|
2574
|
+
const profile = [
|
|
2575
|
+
"# Behavioral Profile",
|
|
2576
|
+
" \t",
|
|
2577
|
+
STALE_HEADER,
|
|
2578
|
+
"",
|
|
2579
|
+
"- Keeps decisions short.",
|
|
2580
|
+
"",
|
|
2581
|
+
].join("\n");
|
|
2582
|
+
|
|
2583
|
+
await storage.writeProfile(profile);
|
|
2584
|
+
|
|
2585
|
+
assert.equal(await storage.readProfile(), profile.replace(STALE_HEADER, FRESH_HEADER));
|
|
2586
|
+
});
|
|
2587
|
+
} finally {
|
|
2588
|
+
t.mock.timers.reset();
|
|
2589
|
+
}
|
|
2590
|
+
});
|
|
2591
|
+
|
|
2592
|
+
test("writeProfile finds a title after a preamble", async (t) => {
|
|
2593
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
2594
|
+
try {
|
|
2595
|
+
await withMemoryDir(async (dir) => {
|
|
2596
|
+
const storage = new StorageManager(dir);
|
|
2597
|
+
const profile = [
|
|
2598
|
+
"Generated by the profile importer.",
|
|
2599
|
+
"",
|
|
2600
|
+
"# Behavioral Profile",
|
|
2601
|
+
"",
|
|
2602
|
+
STALE_HEADER,
|
|
2603
|
+
"",
|
|
2604
|
+
"- Keeps decisions short.",
|
|
2605
|
+
"",
|
|
2606
|
+
].join("\n");
|
|
2607
|
+
|
|
2608
|
+
await storage.writeProfile(profile);
|
|
2609
|
+
|
|
2610
|
+
assert.equal(await storage.readProfile(), profile.replace(STALE_HEADER, FRESH_HEADER));
|
|
2611
|
+
});
|
|
2612
|
+
} finally {
|
|
2613
|
+
t.mock.timers.reset();
|
|
2614
|
+
}
|
|
2615
|
+
});
|
|
2616
|
+
|
|
2617
|
+
test("writeProfile does not treat fenced headings as a profile title", async (t) => {
|
|
2618
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
2619
|
+
try {
|
|
2620
|
+
await withMemoryDir(async (dir) => {
|
|
2621
|
+
const storage = new StorageManager(dir);
|
|
2622
|
+
const profile = ["```shell", "# install dependencies", "```", ""].join("\n");
|
|
2623
|
+
|
|
2624
|
+
await storage.writeProfile(profile);
|
|
2625
|
+
|
|
2626
|
+
assert.equal(await storage.readProfile(), `${FRESH_HEADER}\n\n${profile}`);
|
|
2627
|
+
});
|
|
2628
|
+
} finally {
|
|
2629
|
+
t.mock.timers.reset();
|
|
2630
|
+
}
|
|
2631
|
+
});
|
|
2632
|
+
|
|
2633
|
+
test("writeProfile keeps nested fences from exposing code headings", async (t) => {
|
|
2634
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
2635
|
+
try {
|
|
2636
|
+
await withMemoryDir(async (dir) => {
|
|
2637
|
+
const storage = new StorageManager(dir);
|
|
2638
|
+
const profile = [
|
|
2639
|
+
"````markdown",
|
|
2640
|
+
"```",
|
|
2641
|
+
"# install dependencies",
|
|
2642
|
+
"```",
|
|
2643
|
+
"````",
|
|
2644
|
+
"",
|
|
2645
|
+
].join("\n");
|
|
2646
|
+
|
|
2647
|
+
await storage.writeProfile(profile);
|
|
2648
|
+
|
|
2649
|
+
assert.equal(await storage.readProfile(), `${FRESH_HEADER}\n\n${profile}`);
|
|
2650
|
+
});
|
|
2651
|
+
} finally {
|
|
2652
|
+
t.mock.timers.reset();
|
|
2653
|
+
}
|
|
2654
|
+
});
|
|
2655
|
+
|
|
2656
|
+
test("writeProfile refreshes an off-slot header outside fenced code", async (t) => {
|
|
2657
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
2658
|
+
try {
|
|
2659
|
+
await withMemoryDir(async (dir) => {
|
|
2660
|
+
const storage = new StorageManager(dir);
|
|
2661
|
+
const profile = [
|
|
2662
|
+
"# Behavioral Profile",
|
|
2663
|
+
"",
|
|
2664
|
+
"- Keeps decisions short.",
|
|
2665
|
+
"",
|
|
2666
|
+
"## Notes",
|
|
2667
|
+
"",
|
|
2668
|
+
STALE_HEADER,
|
|
2669
|
+
"",
|
|
2670
|
+
].join("\n");
|
|
2671
|
+
|
|
2672
|
+
await storage.writeProfile(profile);
|
|
2673
|
+
|
|
2674
|
+
assert.equal(await storage.readProfile(), profile.replace(STALE_HEADER, FRESH_HEADER));
|
|
2675
|
+
});
|
|
2676
|
+
} finally {
|
|
2677
|
+
t.mock.timers.reset();
|
|
2678
|
+
}
|
|
2679
|
+
});
|
|
2680
|
+
|
|
2681
|
+
test("writeProfile keeps fence lines with language text inside code", async (t) => {
|
|
2682
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
2683
|
+
try {
|
|
2684
|
+
await withMemoryDir(async (dir) => {
|
|
2685
|
+
const storage = new StorageManager(dir);
|
|
2686
|
+
const profile = [
|
|
2687
|
+
"```markdown",
|
|
2688
|
+
"```js",
|
|
2689
|
+
"# install dependencies",
|
|
2690
|
+
"```",
|
|
2691
|
+
"",
|
|
2692
|
+
].join("\n");
|
|
2693
|
+
|
|
2694
|
+
await storage.writeProfile(profile);
|
|
2695
|
+
|
|
2696
|
+
assert.equal(await storage.readProfile(), `${FRESH_HEADER}\n\n${profile}`);
|
|
2697
|
+
});
|
|
2698
|
+
} finally {
|
|
2699
|
+
t.mock.timers.reset();
|
|
2700
|
+
}
|
|
2701
|
+
});
|
|
2702
|
+
|
|
2703
|
+
test("writeProfile preserves a leading BOM before the title", async (t) => {
|
|
2704
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
2705
|
+
try {
|
|
2706
|
+
await withMemoryDir(async (dir) => {
|
|
2707
|
+
const storage = new StorageManager(dir);
|
|
2708
|
+
const profile = `\uFEFF# Behavioral Profile\n\n${STALE_HEADER}\n\n- Keeps decisions short.\n`;
|
|
2709
|
+
|
|
2710
|
+
await storage.writeProfile(profile);
|
|
2711
|
+
|
|
2712
|
+
assert.equal(await storage.readProfile(), profile.replace(STALE_HEADER, FRESH_HEADER));
|
|
2713
|
+
});
|
|
2714
|
+
} finally {
|
|
2715
|
+
t.mock.timers.reset();
|
|
2716
|
+
}
|
|
2717
|
+
});
|
|
2718
|
+
test("writeProfile refreshes a BOM-prefixed header before the title", async (t) => {
|
|
2719
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
2720
|
+
try {
|
|
2721
|
+
await withMemoryDir(async (dir) => {
|
|
2722
|
+
const storage = new StorageManager(dir);
|
|
2723
|
+
const profile = `\uFEFF${STALE_HEADER}\n# Behavioral Profile\n\n- Keeps metadata.\n`;
|
|
2724
|
+
|
|
2725
|
+
await storage.writeProfile(profile);
|
|
2726
|
+
|
|
2727
|
+
assert.equal(await storage.readProfile(), `\uFEFF${FRESH_HEADER}\n# Behavioral Profile\n\n- Keeps metadata.\n`);
|
|
2728
|
+
});
|
|
2729
|
+
} finally {
|
|
2730
|
+
t.mock.timers.reset();
|
|
2731
|
+
}
|
|
2732
|
+
});
|
|
2733
|
+
test("writeProfile preserves a BOM-prefixed fenced example", async (t) => {
|
|
2734
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
2735
|
+
try {
|
|
2736
|
+
await withMemoryDir(async (dir) => {
|
|
2737
|
+
const storage = new StorageManager(dir);
|
|
2738
|
+
const profile = [
|
|
2739
|
+
"\uFEFF```markdown",
|
|
2740
|
+
"# Literal example",
|
|
2741
|
+
"```",
|
|
2742
|
+
"",
|
|
2743
|
+
"- Keeps fenced content.",
|
|
2744
|
+
"",
|
|
2745
|
+
].join("\n");
|
|
2746
|
+
|
|
2747
|
+
await storage.writeProfile(profile);
|
|
2748
|
+
|
|
2749
|
+
assert.equal(await storage.readProfile(), `\uFEFF${FRESH_HEADER}\n\n${profile.slice(1)}`);
|
|
2750
|
+
});
|
|
2751
|
+
} finally {
|
|
2752
|
+
t.mock.timers.reset();
|
|
2753
|
+
}
|
|
2754
|
+
});
|
|
2755
|
+
|
|
2756
|
+
test("writeProfile preserves timestamp-shaped indented code", async (t) => {
|
|
2757
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
2758
|
+
try {
|
|
2759
|
+
await withMemoryDir(async (dir) => {
|
|
2760
|
+
const storage = new StorageManager(dir);
|
|
2761
|
+
const profile = [
|
|
2762
|
+
"# Behavioral Profile",
|
|
2763
|
+
"",
|
|
2764
|
+
"## Notes",
|
|
2765
|
+
"",
|
|
2766
|
+
" *Last updated: literal example*",
|
|
2767
|
+
"",
|
|
2768
|
+
].join("\n");
|
|
2769
|
+
|
|
2770
|
+
await storage.writeProfile(profile);
|
|
2771
|
+
|
|
2772
|
+
assert.equal(
|
|
2773
|
+
await storage.readProfile(),
|
|
2774
|
+
profile.replace("\n\n## Notes", `\n\n${FRESH_HEADER}\n\n## Notes`),
|
|
2775
|
+
);
|
|
2776
|
+
});
|
|
2777
|
+
} finally {
|
|
2778
|
+
t.mock.timers.reset();
|
|
2779
|
+
}
|
|
2780
|
+
});
|
|
2781
|
+
|
|
2782
|
+
test("writeProfile keeps four-space fence-like lines inside code", async (t) => {
|
|
2783
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
2784
|
+
try {
|
|
2785
|
+
await withMemoryDir(async (dir) => {
|
|
2786
|
+
const storage = new StorageManager(dir);
|
|
2787
|
+
const profile = [
|
|
2788
|
+
"```markdown",
|
|
2789
|
+
" ```",
|
|
2790
|
+
"# install dependencies",
|
|
2791
|
+
"```",
|
|
2792
|
+
"",
|
|
2793
|
+
].join("\n");
|
|
2794
|
+
|
|
2795
|
+
await storage.writeProfile(profile);
|
|
2796
|
+
|
|
2797
|
+
assert.equal(await storage.readProfile(), `${FRESH_HEADER}\n\n${profile}`);
|
|
2798
|
+
});
|
|
2799
|
+
} finally {
|
|
2800
|
+
t.mock.timers.reset();
|
|
2801
|
+
}
|
|
2802
|
+
});
|
|
2803
|
+
|
|
2804
|
+
test("writeProfile keeps leading frontmatter before a header", async (t) => {
|
|
2805
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
2806
|
+
try {
|
|
2807
|
+
await withMemoryDir(async (dir) => {
|
|
2808
|
+
const storage = new StorageManager(dir);
|
|
2809
|
+
const profile = ["---", "source: importer", "---", "", "- Keeps decisions short.", ""].join("\n");
|
|
2810
|
+
|
|
2811
|
+
await storage.writeProfile(profile);
|
|
2812
|
+
|
|
2813
|
+
assert.equal(
|
|
2814
|
+
await storage.readProfile(),
|
|
2815
|
+
`---\nsource: importer\n---\n\n${FRESH_HEADER}\n\n- Keeps decisions short.\n`,
|
|
2816
|
+
);
|
|
2817
|
+
});
|
|
2818
|
+
} finally {
|
|
2819
|
+
t.mock.timers.reset();
|
|
2820
|
+
}
|
|
2821
|
+
});
|
|
2822
|
+
test("writeProfile keeps YAML block-scalar delimiters inside frontmatter", async (t) => {
|
|
2823
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
2824
|
+
try {
|
|
2825
|
+
await withMemoryDir(async (dir) => {
|
|
2826
|
+
const storage = new StorageManager(dir);
|
|
2827
|
+
const profile = [
|
|
2828
|
+
"---",
|
|
2829
|
+
"description: |",
|
|
2830
|
+
" ---",
|
|
2831
|
+
" ...",
|
|
2832
|
+
"---",
|
|
2833
|
+
"",
|
|
2834
|
+
"- Keeps decisions short.",
|
|
2835
|
+
"",
|
|
2836
|
+
].join("\n");
|
|
2837
|
+
|
|
2838
|
+
await storage.writeProfile(profile);
|
|
2839
|
+
|
|
2840
|
+
assert.equal(
|
|
2841
|
+
await storage.readProfile(),
|
|
2842
|
+
`---\ndescription: |\n ---\n ...\n---\n\n${FRESH_HEADER}\n\n- Keeps decisions short.\n`,
|
|
2843
|
+
);
|
|
2844
|
+
});
|
|
2845
|
+
} finally {
|
|
2846
|
+
t.mock.timers.reset();
|
|
2847
|
+
}
|
|
2848
|
+
});
|
|
2849
|
+
|
|
2850
|
+
test("writeProfile preserves timestamp-shaped HTML preformatted code", async (t) => {
|
|
2851
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
2852
|
+
try {
|
|
2853
|
+
await withMemoryDir(async (dir) => {
|
|
2854
|
+
const storage = new StorageManager(dir);
|
|
2855
|
+
const profile = [
|
|
2856
|
+
"# Behavioral Profile",
|
|
2857
|
+
"",
|
|
2858
|
+
"<pre>",
|
|
2859
|
+
"*Last updated: literal example*",
|
|
2860
|
+
"</pre>",
|
|
2861
|
+
"",
|
|
2862
|
+
"- Keeps code examples.",
|
|
2863
|
+
"",
|
|
2864
|
+
].join("\n");
|
|
2865
|
+
|
|
2866
|
+
await storage.writeProfile(profile);
|
|
2867
|
+
|
|
2868
|
+
assert.equal(
|
|
2869
|
+
await storage.readProfile(),
|
|
2870
|
+
profile.replace("\n\n<pre>", `\n\n${FRESH_HEADER}\n\n<pre>`),
|
|
2871
|
+
);
|
|
2872
|
+
});
|
|
2873
|
+
} finally {
|
|
2874
|
+
t.mock.timers.reset();
|
|
2875
|
+
}
|
|
2876
|
+
});
|
|
2877
|
+
test("writeProfile preserves timestamp-shaped raw HTML blocks", async (t) => {
|
|
2878
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
2879
|
+
try {
|
|
2880
|
+
await withMemoryDir(async (dir) => {
|
|
2881
|
+
const storage = new StorageManager(dir);
|
|
2882
|
+
for (const tag of ["textarea", "script", "style", "xmp"]) {
|
|
2883
|
+
const profile = [
|
|
2884
|
+
"# Behavioral Profile",
|
|
2885
|
+
"",
|
|
2886
|
+
`<${tag}>`,
|
|
2887
|
+
"*Last updated: literal example*",
|
|
2888
|
+
`</${tag}>`,
|
|
2889
|
+
"",
|
|
2890
|
+
"- Keeps code examples.",
|
|
2891
|
+
"",
|
|
2892
|
+
].join("\n");
|
|
2893
|
+
|
|
2894
|
+
await storage.writeProfile(profile);
|
|
2895
|
+
|
|
2896
|
+
assert.equal(
|
|
2897
|
+
await storage.readProfile(),
|
|
2898
|
+
profile.replace(`\n\n<${tag}>`, `\n\n${FRESH_HEADER}\n\n<${tag}>`),
|
|
2899
|
+
);
|
|
2900
|
+
}
|
|
2901
|
+
});
|
|
2902
|
+
} finally {
|
|
2903
|
+
t.mock.timers.reset();
|
|
2904
|
+
}
|
|
2905
|
+
});
|
|
2906
|
+
test("writeProfile treats xmp as a blank-terminated HTML block", async (t) => {
|
|
2907
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
2908
|
+
try {
|
|
2909
|
+
await withMemoryDir(async (dir) => {
|
|
2910
|
+
const storage = new StorageManager(dir);
|
|
2911
|
+
const profile = [
|
|
2912
|
+
"Preamble prose.",
|
|
2913
|
+
"",
|
|
2914
|
+
"<xmp>",
|
|
2915
|
+
"# Literal xmp heading",
|
|
2916
|
+
STALE_HEADER,
|
|
2917
|
+
"",
|
|
2918
|
+
"# Behavioral Profile",
|
|
2919
|
+
"",
|
|
2920
|
+
STALE_HEADER,
|
|
2921
|
+
"",
|
|
2922
|
+
"- Keeps metadata after xmp content.",
|
|
2923
|
+
"",
|
|
2924
|
+
].join("\n");
|
|
2925
|
+
|
|
2926
|
+
await storage.writeProfile(profile);
|
|
2927
|
+
|
|
2928
|
+
assert.equal(
|
|
2929
|
+
await storage.readProfile(),
|
|
2930
|
+
[
|
|
2931
|
+
"Preamble prose.",
|
|
2932
|
+
"",
|
|
2933
|
+
"<xmp>",
|
|
2934
|
+
"# Literal xmp heading",
|
|
2935
|
+
STALE_HEADER,
|
|
2936
|
+
"",
|
|
2937
|
+
"# Behavioral Profile",
|
|
2938
|
+
"",
|
|
2939
|
+
FRESH_HEADER,
|
|
2940
|
+
"",
|
|
2941
|
+
"- Keeps metadata after xmp content.",
|
|
2942
|
+
"",
|
|
2943
|
+
].join("\n"),
|
|
2944
|
+
);
|
|
2945
|
+
});
|
|
2946
|
+
} finally {
|
|
2947
|
+
t.mock.timers.reset();
|
|
2948
|
+
}
|
|
2949
|
+
});
|
|
2950
|
+
|
|
2951
|
+
test("writeProfile closes same-line raw HTML blocks", async (t) => {
|
|
2952
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
2953
|
+
try {
|
|
2954
|
+
await withMemoryDir(async (dir) => {
|
|
2955
|
+
const storage = new StorageManager(dir);
|
|
2956
|
+
const profile = [
|
|
2957
|
+
"# Behavioral Profile",
|
|
2958
|
+
"",
|
|
2959
|
+
"<pre>inline </pre>",
|
|
2960
|
+
"# Notes",
|
|
2961
|
+
"",
|
|
2962
|
+
STALE_HEADER,
|
|
2963
|
+
"",
|
|
2964
|
+
"- Keeps metadata after inline code.",
|
|
2965
|
+
"",
|
|
2966
|
+
].join("\n");
|
|
2967
|
+
|
|
2968
|
+
await storage.writeProfile(profile);
|
|
2969
|
+
|
|
2970
|
+
assert.equal(await storage.readProfile(), profile.replace(STALE_HEADER, FRESH_HEADER));
|
|
2971
|
+
});
|
|
2972
|
+
} finally {
|
|
2973
|
+
t.mock.timers.reset();
|
|
2974
|
+
}
|
|
2975
|
+
});
|
|
2976
|
+
test("writeProfile treats same-line raw HTML closures as metadata boundaries", async (t) => {
|
|
2977
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
2978
|
+
try {
|
|
2979
|
+
await withMemoryDir(async (dir) => {
|
|
2980
|
+
const storage = new StorageManager(dir);
|
|
2981
|
+
const profile = [
|
|
2982
|
+
"# Behavioral Profile",
|
|
2983
|
+
"",
|
|
2984
|
+
"<pre></pre>",
|
|
2985
|
+
STALE_HEADER,
|
|
2986
|
+
"",
|
|
2987
|
+
"- Keeps metadata after a same-line raw block.",
|
|
2988
|
+
"",
|
|
2989
|
+
].join("\n");
|
|
2990
|
+
|
|
2991
|
+
await storage.writeProfile(profile);
|
|
2992
|
+
|
|
2993
|
+
assert.equal(await storage.readProfile(), profile.replace(STALE_HEADER, FRESH_HEADER));
|
|
2994
|
+
});
|
|
2995
|
+
} finally {
|
|
2996
|
+
t.mock.timers.reset();
|
|
2997
|
+
}
|
|
2998
|
+
});
|
|
2999
|
+
test("writeProfile closes raw HTML blocks after inline content", async (t) => {
|
|
3000
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
3001
|
+
try {
|
|
3002
|
+
await withMemoryDir(async (dir) => {
|
|
3003
|
+
const storage = new StorageManager(dir);
|
|
3004
|
+
const profile = [
|
|
3005
|
+
"# Behavioral Profile",
|
|
3006
|
+
"",
|
|
3007
|
+
"<pre>",
|
|
3008
|
+
"code</pre>",
|
|
3009
|
+
"# Notes",
|
|
3010
|
+
"",
|
|
3011
|
+
STALE_HEADER,
|
|
3012
|
+
"",
|
|
3013
|
+
"- Keeps metadata after inline code.",
|
|
3014
|
+
"",
|
|
3015
|
+
].join("\n");
|
|
3016
|
+
|
|
3017
|
+
await storage.writeProfile(profile);
|
|
3018
|
+
|
|
3019
|
+
assert.equal(await storage.readProfile(), profile.replace(STALE_HEADER, FRESH_HEADER));
|
|
3020
|
+
});
|
|
3021
|
+
} finally {
|
|
3022
|
+
t.mock.timers.reset();
|
|
3023
|
+
}
|
|
3024
|
+
});
|
|
3025
|
+
test("writeProfile closes raw HTML blocks before sibling markup", async (t) => {
|
|
3026
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
3027
|
+
try {
|
|
3028
|
+
await withMemoryDir(async (dir) => {
|
|
3029
|
+
const storage = new StorageManager(dir);
|
|
3030
|
+
const profile = [
|
|
3031
|
+
"# Behavioral Profile",
|
|
3032
|
+
"",
|
|
3033
|
+
"<pre>",
|
|
3034
|
+
"code</pre><div>",
|
|
3035
|
+
"# Notes",
|
|
3036
|
+
"",
|
|
3037
|
+
STALE_HEADER,
|
|
3038
|
+
"",
|
|
3039
|
+
"- Keeps metadata after sibling markup.",
|
|
3040
|
+
"",
|
|
3041
|
+
].join("\n");
|
|
3042
|
+
|
|
3043
|
+
await storage.writeProfile(profile);
|
|
3044
|
+
|
|
3045
|
+
assert.equal(await storage.readProfile(), profile.replace(STALE_HEADER, FRESH_HEADER));
|
|
3046
|
+
});
|
|
3047
|
+
} finally {
|
|
3048
|
+
t.mock.timers.reset();
|
|
3049
|
+
}
|
|
3050
|
+
});
|
|
3051
|
+
test("writeProfile keeps self-closing raw HTML blocks opaque", async (t) => {
|
|
3052
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
3053
|
+
try {
|
|
3054
|
+
await withMemoryDir(async (dir) => {
|
|
3055
|
+
const storage = new StorageManager(dir);
|
|
3056
|
+
const profile = [
|
|
3057
|
+
"# Behavioral Profile",
|
|
3058
|
+
"",
|
|
3059
|
+
"<pre/>",
|
|
3060
|
+
"*Last updated: literal example*",
|
|
3061
|
+
"",
|
|
3062
|
+
"- Keeps self-closing raw HTML opaque.",
|
|
3063
|
+
"",
|
|
3064
|
+
].join("\n");
|
|
3065
|
+
|
|
3066
|
+
await storage.writeProfile(profile);
|
|
3067
|
+
|
|
3068
|
+
assert.equal(
|
|
3069
|
+
await storage.readProfile(),
|
|
3070
|
+
profile.replace("\n\n<pre/>", `\n\n${FRESH_HEADER}\n\n<pre/>`),
|
|
3071
|
+
);
|
|
3072
|
+
});
|
|
3073
|
+
} finally {
|
|
3074
|
+
t.mock.timers.reset();
|
|
3075
|
+
}
|
|
3076
|
+
});
|
|
3077
|
+
test("writeProfile keeps no-space self-closing raw tags in prose", async (t) => {
|
|
3078
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
3079
|
+
try {
|
|
3080
|
+
await withMemoryDir(async (dir) => {
|
|
3081
|
+
const storage = new StorageManager(dir);
|
|
3082
|
+
const profile = [
|
|
3083
|
+
"*Last updated: literal example*",
|
|
3084
|
+
"<pre/>",
|
|
3085
|
+
"# Behavioral Profile",
|
|
3086
|
+
"",
|
|
3087
|
+
STALE_HEADER,
|
|
3088
|
+
"",
|
|
3089
|
+
"- Keeps inline raw tags as prose.",
|
|
3090
|
+
"",
|
|
3091
|
+
].join("\n");
|
|
3092
|
+
|
|
3093
|
+
await storage.writeProfile(profile);
|
|
3094
|
+
|
|
3095
|
+
assert.equal(
|
|
3096
|
+
await storage.readProfile(),
|
|
3097
|
+
[
|
|
3098
|
+
"*Last updated: literal example*",
|
|
3099
|
+
"<pre/>",
|
|
3100
|
+
"# Behavioral Profile",
|
|
3101
|
+
"",
|
|
3102
|
+
FRESH_HEADER,
|
|
3103
|
+
"",
|
|
3104
|
+
"- Keeps inline raw tags as prose.",
|
|
3105
|
+
"",
|
|
3106
|
+
].join("\n"),
|
|
3107
|
+
);
|
|
3108
|
+
});
|
|
3109
|
+
} finally {
|
|
3110
|
+
t.mock.timers.reset();
|
|
3111
|
+
}
|
|
3112
|
+
});
|
|
3113
|
+
test("writeProfile keeps spaced self-closing raw blocks opaque through EOF", async (t) => {
|
|
3114
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
3115
|
+
try {
|
|
3116
|
+
await withMemoryDir(async (dir) => {
|
|
3117
|
+
const storage = new StorageManager(dir);
|
|
3118
|
+
const profile = [
|
|
3119
|
+
"# Behavioral Profile",
|
|
3120
|
+
"",
|
|
3121
|
+
"<pre />",
|
|
3122
|
+
"",
|
|
3123
|
+
"*Last updated: literal example*",
|
|
3124
|
+
"",
|
|
3125
|
+
"- Keeps the spaced raw block opaque.",
|
|
3126
|
+
"",
|
|
3127
|
+
].join("\n");
|
|
3128
|
+
|
|
3129
|
+
await storage.writeProfile(profile);
|
|
3130
|
+
|
|
3131
|
+
assert.equal(
|
|
3132
|
+
await storage.readProfile(),
|
|
3133
|
+
[
|
|
3134
|
+
"# Behavioral Profile",
|
|
3135
|
+
"",
|
|
3136
|
+
FRESH_HEADER,
|
|
3137
|
+
"",
|
|
3138
|
+
"<pre />",
|
|
3139
|
+
"",
|
|
3140
|
+
"*Last updated: literal example*",
|
|
3141
|
+
"",
|
|
3142
|
+
"- Keeps the spaced raw block opaque.",
|
|
3143
|
+
"",
|
|
3144
|
+
].join("\n"),
|
|
3145
|
+
);
|
|
3146
|
+
});
|
|
3147
|
+
} finally {
|
|
3148
|
+
t.mock.timers.reset();
|
|
3149
|
+
}
|
|
3150
|
+
});
|
|
3151
|
+
test("writeProfile keeps incomplete raw HTML closing markers inside content", async (t) => {
|
|
3152
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
3153
|
+
try {
|
|
3154
|
+
await withMemoryDir(async (dir) => {
|
|
3155
|
+
const storage = new StorageManager(dir);
|
|
3156
|
+
const profile = [
|
|
3157
|
+
"# Behavioral Profile",
|
|
3158
|
+
"",
|
|
3159
|
+
"<pre>",
|
|
3160
|
+
"literal </pre remains content",
|
|
3161
|
+
"*Last updated: literal example*",
|
|
3162
|
+
"</pre>",
|
|
3163
|
+
"",
|
|
3164
|
+
"- Keeps raw HTML content.",
|
|
3165
|
+
"",
|
|
3166
|
+
].join("\n");
|
|
3167
|
+
|
|
3168
|
+
await storage.writeProfile(profile);
|
|
3169
|
+
|
|
3170
|
+
assert.equal(
|
|
3171
|
+
await storage.readProfile(),
|
|
3172
|
+
profile.replace("\n\n<pre>", `\n\n${FRESH_HEADER}\n\n<pre>`),
|
|
3173
|
+
);
|
|
3174
|
+
});
|
|
3175
|
+
} finally {
|
|
3176
|
+
t.mock.timers.reset();
|
|
3177
|
+
}
|
|
3178
|
+
});
|
|
3179
|
+
|
|
3180
|
+
test("writeProfile preserves timestamp-shaped generic HTML blocks", async (t) => {
|
|
3181
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
3182
|
+
try {
|
|
3183
|
+
await withMemoryDir(async (dir) => {
|
|
3184
|
+
const storage = new StorageManager(dir);
|
|
3185
|
+
for (const tagLine of ["</custom-widget>", "<custom-widget />"]) {
|
|
3186
|
+
const profile = [
|
|
3187
|
+
"# Behavioral Profile",
|
|
3188
|
+
"",
|
|
3189
|
+
tagLine,
|
|
3190
|
+
STALE_HEADER,
|
|
3191
|
+
"",
|
|
3192
|
+
"- Keeps custom HTML opaque.",
|
|
3193
|
+
"",
|
|
3194
|
+
].join("\n");
|
|
3195
|
+
|
|
3196
|
+
await storage.writeProfile(profile);
|
|
3197
|
+
|
|
3198
|
+
assert.equal(
|
|
3199
|
+
await storage.readProfile(),
|
|
3200
|
+
profile.replace(`\n\n${tagLine}`, `\n\n${FRESH_HEADER}\n\n${tagLine}`),
|
|
3201
|
+
);
|
|
3202
|
+
}
|
|
3203
|
+
});
|
|
3204
|
+
} finally {
|
|
3205
|
+
t.mock.timers.reset();
|
|
3206
|
+
}
|
|
3207
|
+
});
|
|
3208
|
+
test("writeProfile keeps self-closing built-in HTML blocks opaque", async (t) => {
|
|
3209
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
3210
|
+
try {
|
|
3211
|
+
await withMemoryDir(async (dir) => {
|
|
3212
|
+
const storage = new StorageManager(dir);
|
|
3213
|
+
const profile = [
|
|
3214
|
+
"# Behavioral Profile",
|
|
3215
|
+
"",
|
|
3216
|
+
"<div/>",
|
|
3217
|
+
"*Last updated: literal example*",
|
|
3218
|
+
"",
|
|
3219
|
+
"- Keeps self-closing HTML opaque.",
|
|
3220
|
+
"",
|
|
3221
|
+
].join("\n");
|
|
3222
|
+
|
|
3223
|
+
await storage.writeProfile(profile);
|
|
3224
|
+
|
|
3225
|
+
assert.equal(
|
|
3226
|
+
await storage.readProfile(),
|
|
3227
|
+
profile.replace("\n\n<div/>", `\n\n${FRESH_HEADER}\n\n<div/>`),
|
|
3228
|
+
);
|
|
3229
|
+
});
|
|
3230
|
+
} finally {
|
|
3231
|
+
t.mock.timers.reset();
|
|
3232
|
+
}
|
|
3233
|
+
});
|
|
3234
|
+
|
|
3235
|
+
test("writeProfile preserves timestamp-shaped HTML comments", async (t) => {
|
|
3236
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
3237
|
+
try {
|
|
3238
|
+
await withMemoryDir(async (dir) => {
|
|
3239
|
+
const storage = new StorageManager(dir);
|
|
3240
|
+
const profile = [
|
|
3241
|
+
"<!--",
|
|
3242
|
+
"# example",
|
|
3243
|
+
"*Last updated: literal example*",
|
|
3244
|
+
"-->",
|
|
3245
|
+
"",
|
|
3246
|
+
"# Behavioral Profile",
|
|
3247
|
+
"",
|
|
3248
|
+
"- Keeps comments.",
|
|
3249
|
+
"",
|
|
3250
|
+
].join("\n");
|
|
3251
|
+
|
|
3252
|
+
await storage.writeProfile(profile);
|
|
3253
|
+
|
|
3254
|
+
assert.equal(
|
|
3255
|
+
await storage.readProfile(),
|
|
3256
|
+
profile.replace("# Behavioral Profile\n\n", `# Behavioral Profile\n\n${FRESH_HEADER}\n\n`),
|
|
3257
|
+
);
|
|
3258
|
+
});
|
|
3259
|
+
} finally {
|
|
3260
|
+
t.mock.timers.reset();
|
|
3261
|
+
}
|
|
3262
|
+
});
|
|
3263
|
+
|
|
3264
|
+
test("writeProfile recognizes HTML terminators before trailing text", async (t) => {
|
|
3265
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
3266
|
+
try {
|
|
3267
|
+
await withMemoryDir(async (dir) => {
|
|
3268
|
+
const storage = new StorageManager(dir);
|
|
3269
|
+
const profile = [
|
|
3270
|
+
"# Behavioral Profile",
|
|
3271
|
+
"",
|
|
3272
|
+
"<!--",
|
|
3273
|
+
"comment --> trailing",
|
|
3274
|
+
STALE_HEADER,
|
|
3275
|
+
"",
|
|
3276
|
+
"- Keeps comment terminators as metadata boundaries.",
|
|
3277
|
+
"",
|
|
3278
|
+
].join("\n");
|
|
3279
|
+
|
|
3280
|
+
await storage.writeProfile(profile);
|
|
3281
|
+
|
|
3282
|
+
assert.equal(await storage.readProfile(), profile.replace(STALE_HEADER, FRESH_HEADER));
|
|
3283
|
+
});
|
|
3284
|
+
} finally {
|
|
3285
|
+
t.mock.timers.reset();
|
|
3286
|
+
}
|
|
3287
|
+
});
|
|
3288
|
+
test("writeProfile preserves code examples that mention Last updated", async (t) => {
|
|
3289
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
3290
|
+
try {
|
|
3291
|
+
await withMemoryDir(async (dir) => {
|
|
3292
|
+
const storage = new StorageManager(dir);
|
|
3293
|
+
const profile = [
|
|
3294
|
+
"# Behavioral Profile",
|
|
3295
|
+
"",
|
|
3296
|
+
"## Notes",
|
|
3297
|
+
"",
|
|
3298
|
+
" Last updated: database field",
|
|
3299
|
+
"",
|
|
3300
|
+
"- Uses markdown code examples.",
|
|
3301
|
+
"",
|
|
3302
|
+
].join("\n");
|
|
3303
|
+
|
|
3304
|
+
await storage.writeProfile(profile);
|
|
3305
|
+
|
|
3306
|
+
assert.equal(
|
|
3307
|
+
await storage.readProfile(),
|
|
3308
|
+
profile.replace("\n\n## Notes", `\n\n${FRESH_HEADER}\n\n## Notes`),
|
|
3309
|
+
);
|
|
3310
|
+
});
|
|
3311
|
+
} finally {
|
|
3312
|
+
t.mock.timers.reset();
|
|
3313
|
+
}
|
|
3314
|
+
});
|
|
3315
|
+
|
|
3316
|
+
test("appendToProfile preserves timestamp-shaped fenced code", async (t) => {
|
|
3317
|
+
t.mock.timers.enable({ apis: ["Date"], now: Date.parse(WRITE_TIME) });
|
|
3318
|
+
try {
|
|
3319
|
+
await withMemoryDir(async (dir) => {
|
|
3320
|
+
const storage = new StorageManager(dir);
|
|
3321
|
+
const profile = [
|
|
3322
|
+
"# Behavioral Profile",
|
|
3323
|
+
"",
|
|
3324
|
+
"## Notes",
|
|
3325
|
+
"",
|
|
3326
|
+
"```markdown",
|
|
3327
|
+
STALE_HEADER,
|
|
3328
|
+
"```",
|
|
3329
|
+
"",
|
|
3330
|
+
"- Uses markdown code examples.",
|
|
3331
|
+
"",
|
|
3332
|
+
].join("\n");
|
|
3333
|
+
await writeFile(path.join(dir, "profile.md"), profile, "utf8");
|
|
3334
|
+
|
|
3335
|
+
await storage.appendToProfile(["Adds durable details."]);
|
|
3336
|
+
|
|
3337
|
+
assert.equal(
|
|
3338
|
+
await storage.readProfile(),
|
|
3339
|
+
profile.replace("\n\n## Notes", `\n\n${FRESH_HEADER}\n\n## Notes`).trimEnd() +
|
|
3340
|
+
"\n- Adds durable details.\n",
|
|
3341
|
+
);
|
|
3342
|
+
});
|
|
3343
|
+
} finally {
|
|
3344
|
+
t.mock.timers.reset();
|
|
3345
|
+
}
|
|
3346
|
+
});
|
|
3347
|
+
|
|
3348
|
+
test("readProfile does not rewrite a stale header", async () => {
|
|
3349
|
+
await withMemoryDir(async (dir) => {
|
|
3350
|
+
const storage = new StorageManager(dir);
|
|
3351
|
+
const staleProfile = `# Behavioral Profile\n\n${STALE_HEADER}\n\n- Reads are side-effect free.\n`;
|
|
3352
|
+
const profilePath = path.join(dir, "profile.md");
|
|
3353
|
+
await writeFile(profilePath, staleProfile, "utf8");
|
|
3354
|
+
|
|
3355
|
+
assert.equal(await storage.readProfile(), staleProfile);
|
|
3356
|
+
assert.equal(await readFile(profilePath, "utf8"), staleProfile);
|
|
3357
|
+
});
|
|
3358
|
+
});
|