From fd11c2771bfe2e1533722297faad3daca331daca Mon Sep 17 00:00:00 2001 From: angelTomo9 <144371630+angelTomo9@users.noreply.github.com> Date: Mon, 31 Aug 2026 09:11:09 +0200 Subject: [PATCH] feat(leathercraft): add ancient runic leather saddle bag bench, rivet buckle, and mount haversack engine --- .../lib/ancientRunicLeatherSaddleBagBench.ts | 261 ++++++++++++++++++ .../ancientRunicLeatherSaddleBagBench.test.ts | 154 +++++++++++ 2 files changed, 415 insertions(+) create mode 100644 api/src/lib/ancientRunicLeatherSaddleBagBench.ts create mode 100644 api/src/tests/ancientRunicLeatherSaddleBagBench.test.ts diff --git a/api/src/lib/ancientRunicLeatherSaddleBagBench.ts b/api/src/lib/ancientRunicLeatherSaddleBagBench.ts new file mode 100644 index 00000000..ed39a2b1 --- /dev/null +++ b/api/src/lib/ancientRunicLeatherSaddleBagBench.ts @@ -0,0 +1,261 @@ +import crypto from "node:crypto"; + +/** + * Ancient Runic Leather Saddle Bag Bench, Brass Rivet Buckle & Mount Pannier Engine for OpenAO MMORPG. + * Simulates saddle bag stitching benches and pannier riveting anvils (Oak Saddle Bag Bench, Runic Ironwood Mount Rig, Celestial Void Seraphic Caravan Sanctum), + * raw tanned horsehide pannier blanks and brass riveted buckle sets (Tanned Horsehide Pannier Blank, Brass Riveted Buckle Set, Celestial Void Starlight Sovereign Caravan Leather), + * courier fast-access saddle bags and seraphic dimension-folded haversack recipes (Courier Fast-Access Saddle Bag, Caravan Heavy Double Pannier, Celestial Void Seraphic Dimension-Folded Mount Haversack), + * independent weight distribution & quick-loot accessibility ratings (scaled across catalog baselines ~14% to 100%), calibrated clamped mount carrying capacity bonus and clamped mount stamina depletion mitigation scaling, + * upfront leather material deduction on all craft attempts, consistent remainingProvidedLeathers return shapes across all paths, immutable bench cloning for safe rollbacks on both craft and maintain operations, cached static catalog maxima, crypto-secure default gameplay rolls strictly in [0, 1), authoritative catalog power ratio without dead instance fields, and saddle bag bench maintenance. + */ + +export type SaddleBagBenchType = "OAK_SADDLE_BAG_BENCH" | "RUNIC_IRONWOOD_MOUNT_RIG" | "CELESTIAL_VOID_SERAPHIC_CARAVAN_SANCTUM"; +export type RawLeatherSaddleBagType = "TANNED_HORSEHIDE_PANNIER_BLANK" | "BRASS_RIVETED_BUCKLE_SET" | "CELESTIAL_VOID_STARLIGHT_SOVEREIGN_CARAVAN_LEATHER"; +export type MountSaddleBagRecipeType = "COURIER_FAST_ACCESS_SADDLE_BAG" | "CARAVAN_HEAVY_DOUBLE_PANNIER" | "CELESTIAL_VOID_SERAPHIC_DIMENSION_FOLDED_MOUNT_HAVERSACK"; + +export interface SaddleBagBenchData { + benchType: SaddleBagBenchType; + maxDurability: number; + leathercraftPower: number; + baseSuccessRatePercent: number; // 0 to 100 + quicklootBonusPercent: number; +} + +export interface MountSaddleBagRecipeData { + recipeType: MountSaddleBagRecipeType; + requiredLeatherType: RawLeatherSaddleBagType; + requiredLeatherCount: number; + baseMountCarryingCapacityBonusPercent: number; + baseMountStaminaDepletionMitigationPercent: number; +} + +export interface ActiveSaddleBagBench { + benchId: string; + leatherworkerPlayerId: string; + benchType: SaddleBagBenchType; + currentDurability: number; + maxDurability: number; + isFunctional: boolean; +} + +export interface CraftedMountSaddleBag { + saddleBagId: string; + recipeType: MountSaddleBagRecipeType; + finalMountCarryingCapacityBonusPercent: number; + finalMountStaminaDepletionMitigationPercent: number; + quicklootAccessibilityPercent: number; // Scaled rating (clamped 0 to 100%, with catalog bench baselines ~14% to 100%) + consumedLeatherCount: number; + consumedLeatherType: RawLeatherSaddleBagType; + remainingProvidedLeathers: RawLeatherSaddleBagType[]; + craftedEpochMs: number; +} + +export const SADDLE_BAG_BENCH_CATALOG: Record = { + OAK_SADDLE_BAG_BENCH: { benchType: "OAK_SADDLE_BAG_BENCH", maxDurability: 75, leathercraftPower: 25, baseSuccessRatePercent: 85, quicklootBonusPercent: 10 }, + RUNIC_IRONWOOD_MOUNT_RIG: { benchType: "RUNIC_IRONWOOD_MOUNT_RIG", maxDurability: 170, leathercraftPower: 65, baseSuccessRatePercent: 92, quicklootBonusPercent: 20 }, + CELESTIAL_VOID_SERAPHIC_CARAVAN_SANCTUM: { benchType: "CELESTIAL_VOID_SERAPHIC_CARAVAN_SANCTUM", maxDurability: 310, leathercraftPower: 120, baseSuccessRatePercent: 99, quicklootBonusPercent: 35 }, +}; + +export const SADDLE_BAG_RECIPE_CATALOG: Record = { + COURIER_FAST_ACCESS_SADDLE_BAG: { recipeType: "COURIER_FAST_ACCESS_SADDLE_BAG", requiredLeatherType: "TANNED_HORSEHIDE_PANNIER_BLANK", requiredLeatherCount: 2, baseMountCarryingCapacityBonusPercent: 20, baseMountStaminaDepletionMitigationPercent: 10 }, + CARAVAN_HEAVY_DOUBLE_PANNIER: { recipeType: "CARAVAN_HEAVY_DOUBLE_PANNIER", requiredLeatherType: "BRASS_RIVETED_BUCKLE_SET", requiredLeatherCount: 2, baseMountCarryingCapacityBonusPercent: 45, baseMountStaminaDepletionMitigationPercent: 25 }, + CELESTIAL_VOID_SERAPHIC_DIMENSION_FOLDED_MOUNT_HAVERSACK: { recipeType: "CELESTIAL_VOID_SERAPHIC_DIMENSION_FOLDED_MOUNT_HAVERSACK", requiredLeatherType: "CELESTIAL_VOID_STARLIGHT_SOVEREIGN_CARAVAN_LEATHER", requiredLeatherCount: 2, baseMountCarryingCapacityBonusPercent: 80, baseMountStaminaDepletionMitigationPercent: 60 }, +}; + +export class AncientRunicLeatherSaddleBagBenchEngine { + public static readonly DURABILITY_COST_PER_CRAFT = 10; + + /** + * Cached static catalog maxima to prevent runtime array reallocation. + */ + public static readonly CATALOG_MAXIMA = { + maxPower: Math.max(...Object.values(SADDLE_BAG_BENCH_CATALOG).map(b => b.leathercraftPower), 1), + maxBonus: Math.max(...Object.values(SADDLE_BAG_BENCH_CATALOG).map(b => b.quicklootBonusPercent), 1), + }; + + /** + * Generates a crypto-secure UUID or 128-bit hex string using node:crypto. + */ + private static generateSecureId(): string { + if (typeof crypto.randomUUID === "function") { + return crypto.randomUUID(); + } + return crypto.randomBytes(16).toString("hex"); + } + + /** + * Generates a cryptographically secure random float strictly in [0, 1). + */ + public static generateSecureRoll(): number { + if (typeof crypto.randomInt === "function") { + return crypto.randomInt(0, 1000000) / 1000000; + } + return crypto.randomBytes(4).readUInt32LE(0) / 0x100000000; + } + + /** + * Constructs and initializes a saddle bag stitching bench or mount pannier rig. + */ + public static constructBench( + leatherworkerPlayerId: string, + benchType: SaddleBagBenchType + ): ActiveSaddleBagBench { + const data = SADDLE_BAG_BENCH_CATALOG[benchType]; + if (!data) { + throw new Error(`Unsupported saddle bag bench type: ${String(benchType)}`); + } + + const uuid = this.generateSecureId(); + + return { + benchId: `bench_${benchType.toLowerCase()}_${uuid}`, + leatherworkerPlayerId, + benchType, + currentDurability: data.maxDurability, + maxDurability: data.maxDurability, + isFunctional: true, + }; + } + + /** + * Stitches and rivets horsehide pannier blanks and brass buckles into mount saddle bags. + * Returns an updated clone of `bench` leaving the input instance immutable. + */ + public static craftSaddleBag( + bench: ActiveSaddleBagBench, + recipeType: MountSaddleBagRecipeType, + providedLeathers: RawLeatherSaddleBagType[], + craftRoll?: number, + accessibilityRoll?: number, + currentEpochMs = Date.now() + ): { success: boolean; saddleBag?: CraftedMountSaddleBag; updatedBench?: ActiveSaddleBagBench; remainingDurability: number; remainingProvidedLeathers: RawLeatherSaddleBagType[]; reason?: string } { + const fallbackLeathers = Array.isArray(providedLeathers) ? [...providedLeathers] : []; + + if (!bench || !bench.isFunctional || bench.currentDurability < this.DURABILITY_COST_PER_CRAFT) { + return { + success: false, + updatedBench: bench ? { ...bench } : undefined, + remainingDurability: bench?.currentDurability ?? 0, + remainingProvidedLeathers: fallbackLeathers, + reason: `Saddle bag bench is warped or lacks durability (requires ${this.DURABILITY_COST_PER_CRAFT}).`, + }; + } + + const benchData = SADDLE_BAG_BENCH_CATALOG[bench.benchType]; + if (!benchData) { + return { success: false, updatedBench: { ...bench }, remainingDurability: bench.currentDurability, remainingProvidedLeathers: fallbackLeathers, reason: `Unknown bench model: ${String(bench.benchType)}` }; + } + + const recipe = SADDLE_BAG_RECIPE_CATALOG[recipeType]; + if (!recipe) { + return { success: false, updatedBench: { ...bench }, remainingDurability: bench.currentDurability, remainingProvidedLeathers: fallbackLeathers, reason: `Unknown saddle bag recipe: ${String(recipeType)}` }; + } + + if (!Array.isArray(providedLeathers)) { + return { success: false, updatedBench: { ...bench }, remainingDurability: bench.currentDurability, remainingProvidedLeathers: [], reason: "Invalid leathers array." }; + } + + // Count matching leather materials + const matchingCount = providedLeathers.filter(l => l === recipe.requiredLeatherType).length; + if (matchingCount < recipe.requiredLeatherCount) { + return { + success: false, + updatedBench: { ...bench }, + remainingDurability: bench.currentDurability, + remainingProvidedLeathers: fallbackLeathers, + reason: `Insufficient saddle bag leather/buckles: requires ${recipe.requiredLeatherCount}x ${recipe.requiredLeatherType}, provided ${matchingCount}.`, + }; + } + + // Create updated bench clone + const updatedBench = { ...bench }; + + // Deduct durability on clone + updatedBench.currentDurability -= this.DURABILITY_COST_PER_CRAFT; + if (updatedBench.currentDurability < this.DURABILITY_COST_PER_CRAFT) { + updatedBench.currentDurability = Math.max(0, updatedBench.currentDurability); + updatedBench.isFunctional = false; + } + + // Deduct materials upfront on all craft attempts + const remaining = [...providedLeathers]; + let removed = 0; + for (let i = remaining.length - 1; i >= 0 && removed < recipe.requiredLeatherCount; i--) { + if (remaining[i] === recipe.requiredLeatherType) { + remaining.splice(i, 1); + removed++; + } + } + + const safeRoll = typeof craftRoll === "number" && Number.isFinite(craftRoll) ? Math.max(0, Math.min(1, craftRoll)) : this.generateSecureRoll(); + const rollPercent = safeRoll * 100; + + if (rollPercent > benchData.baseSuccessRatePercent) { + return { + success: false, + updatedBench, + remainingDurability: updatedBench.currentDurability, + remainingProvidedLeathers: remaining, + reason: `Gusset torn: brass rivet punch sheared pannier flap seam, rolled ${rollPercent.toFixed(1)}, needed <= ${benchData.baseSuccessRatePercent}.`, + }; + } + + // Calculate independent quick-loot accessibility score dynamically using cached catalog maxima & authoritative catalog values (clamped 0% to 100%, scaling across catalog baselines) + const { maxPower, maxBonus } = this.CATALOG_MAXIMA; + const safeAccessibilityRoll = typeof accessibilityRoll === "number" && Number.isFinite(accessibilityRoll) ? Math.max(0, Math.min(1, accessibilityRoll)) : this.generateSecureRoll(); + const powerRatio = Math.min(1.0, benchData.leathercraftPower / maxPower); + const bonusPoints = (benchData.quicklootBonusPercent / maxBonus) * 20; + const accessibilityScore = Math.max(0, Math.min(100, Math.round( + (safeAccessibilityRoll * 40) + (powerRatio * 40) + bonusPoints + ))); + const qualityMultiplier = 0.8 + ((accessibilityScore / 100) * 0.4); // 0.8 to 1.2x + + const finalCapacityBonus = Math.max(0, Math.min(100, Math.round(recipe.baseMountCarryingCapacityBonusPercent * qualityMultiplier))); + const finalStaminaMitigate = Math.max(0, Math.min(100, Math.round(recipe.baseMountStaminaDepletionMitigationPercent * qualityMultiplier))); + + const uuid = this.generateSecureId(); + + const saddleBag: CraftedMountSaddleBag = { + saddleBagId: `saddlebag_${recipeType.toLowerCase()}_${uuid}`, + recipeType, + finalMountCarryingCapacityBonusPercent: finalCapacityBonus, + finalMountStaminaDepletionMitigationPercent: finalStaminaMitigate, + quicklootAccessibilityPercent: accessibilityScore, + consumedLeatherCount: recipe.requiredLeatherCount, + consumedLeatherType: recipe.requiredLeatherType, + remainingProvidedLeathers: remaining, + craftedEpochMs: currentEpochMs, + }; + + return { + success: true, + saddleBag, + updatedBench, + remainingDurability: updatedBench.currentDurability, + remainingProvidedLeathers: remaining, + }; + } + + /** + * Re-tightens rivet punch anvils and maintains saddle bag bench. + * Returns an updated clone of `bench` leaving the input instance immutable. + */ + public static maintainBench( + bench: ActiveSaddleBagBench, + repairAmount = 50 + ): { success: boolean; updatedBench?: ActiveSaddleBagBench; newDurability: number; isFunctional: boolean } { + if (!bench) return { success: false, newDurability: 0, isFunctional: false }; + + const updatedBench = { ...bench }; + const amt = Number.isFinite(repairAmount) ? Math.max(0, repairAmount) : 50; + updatedBench.currentDurability = Math.min(updatedBench.maxDurability, updatedBench.currentDurability + amt); + updatedBench.isFunctional = updatedBench.currentDurability >= this.DURABILITY_COST_PER_CRAFT; + + return { + success: true, + updatedBench, + newDurability: updatedBench.currentDurability, + isFunctional: updatedBench.isFunctional, + }; + } +} \ No newline at end of file diff --git a/api/src/tests/ancientRunicLeatherSaddleBagBench.test.ts b/api/src/tests/ancientRunicLeatherSaddleBagBench.test.ts new file mode 100644 index 00000000..bc746cc4 --- /dev/null +++ b/api/src/tests/ancientRunicLeatherSaddleBagBench.test.ts @@ -0,0 +1,154 @@ +import { describe, it, expect } from "vitest"; +import { AncientRunicLeatherSaddleBagBenchEngine } from "../lib/ancientRunicLeatherSaddleBagBench"; +import type { ActiveSaddleBagBench } from "../lib/ancientRunicLeatherSaddleBagBench"; + +describe("AncientRunicLeatherSaddleBagBenchEngine Saddle Bag Benches & Mount Panniers", () => { + it("crafts Celestial Void Seraphic Dimension-Folded Mount Haversack in Caravan Sanctum achieving 100% accessibility and returns spliced leathers", () => { + const bench = AncientRunicLeatherSaddleBagBenchEngine.constructBench("leather_01", "CELESTIAL_VOID_SERAPHIC_CARAVAN_SANCTUM"); + expect(bench.benchType).toBe("CELESTIAL_VOID_SERAPHIC_CARAVAN_SANCTUM"); + expect(bench.currentDurability).toBe(310); + + const initialLeathers = [ + "CELESTIAL_VOID_STARLIGHT_SOVEREIGN_CARAVAN_LEATHER", + "CELESTIAL_VOID_STARLIGHT_SOVEREIGN_CARAVAN_LEATHER", + "CELESTIAL_VOID_STARLIGHT_SOVEREIGN_CARAVAN_LEATHER" + ] as any[]; + + const craftRes = AncientRunicLeatherSaddleBagBenchEngine.craftSaddleBag( + bench, + "CELESTIAL_VOID_SERAPHIC_DIMENSION_FOLDED_MOUNT_HAVERSACK", + initialLeathers, + 0.1, // Success roll + 1.0, // Accessibility roll 1.0 -> 40 + 40 + 20 = 100% + 100000 + ); + + expect(craftRes.success).toBe(true); + expect(craftRes.saddleBag?.recipeType).toBe("CELESTIAL_VOID_SERAPHIC_DIMENSION_FOLDED_MOUNT_HAVERSACK"); + expect(craftRes.saddleBag?.quicklootAccessibilityPercent).toBe(100); + expect(craftRes.saddleBag?.finalMountCarryingCapacityBonusPercent).toBe(96); // 80 * 1.20 = 96% + expect(craftRes.saddleBag?.finalMountStaminaDepletionMitigationPercent).toBe(72); // 60 * 1.20 = 72% + expect(craftRes.saddleBag?.consumedLeatherCount).toBe(2); + expect(craftRes.saddleBag?.consumedLeatherType).toBe("CELESTIAL_VOID_STARLIGHT_SOVEREIGN_CARAVAN_LEATHER"); + expect(craftRes.saddleBag?.remainingProvidedLeathers.length).toBe(1); + expect(craftRes.remainingDurability).toBe(300); // 310 - 10 + }); + + it("verifies mid-range accessibility roll and sub-100% quality scaling on Oak saddle bag bench", () => { + const bench = AncientRunicLeatherSaddleBagBenchEngine.constructBench("leather_mid", "OAK_SADDLE_BAG_BENCH"); + // powerRatio = 25/120 = 0.20833, bonusPoints = (10/35)*20 = 5.714 + // safeAccessibilityRoll = 0.5 -> 0.5 * 40 = 20 + // accessibilityScore = Math.round(20 + 8.333 + 5.714) = 34 + // qualityMultiplier = 0.8 + (34/100)*0.4 = 0.8 + 0.136 = 0.936 + // finalCapacityBonus = Math.round(20 * 0.936) = 19 + // finalStaminaMitigate = Math.round(10 * 0.936) = 9 + const craftRes = AncientRunicLeatherSaddleBagBenchEngine.craftSaddleBag( + bench, + "COURIER_FAST_ACCESS_SADDLE_BAG", + ["TANNED_HORSEHIDE_PANNIER_BLANK", "TANNED_HORSEHIDE_PANNIER_BLANK"], + 0.1, + 0.5 + ); + + expect(craftRes.success).toBe(true); + expect(craftRes.saddleBag?.quicklootAccessibilityPercent).toBe(34); + expect(craftRes.saddleBag?.finalMountCarryingCapacityBonusPercent).toBe(19); + expect(craftRes.saddleBag?.finalMountStaminaDepletionMitigationPercent).toBe(9); + }); + + it("handles bench becoming non-functional after successful craft when durability falls below threshold", () => { + const bench = AncientRunicLeatherSaddleBagBenchEngine.constructBench("leather_wear", "OAK_SADDLE_BAG_BENCH"); + bench.currentDurability = 15; + expect(bench.isFunctional).toBe(true); + + // First craft succeeds: 15 - 10 = 5 (< 10), so isFunctional flips to false in updatedBench + const res1 = AncientRunicLeatherSaddleBagBenchEngine.craftSaddleBag( + bench, + "COURIER_FAST_ACCESS_SADDLE_BAG", + ["TANNED_HORSEHIDE_PANNIER_BLANK", "TANNED_HORSEHIDE_PANNIER_BLANK"], + 0.1 + ); + expect(res1.success).toBe(true); + expect(res1.remainingDurability).toBe(5); + expect(res1.updatedBench?.isFunctional).toBe(false); + + // Subsequent craft on updated bench is rejected and returns fallback array + const res2 = AncientRunicLeatherSaddleBagBenchEngine.craftSaddleBag( + res1.updatedBench!, + "COURIER_FAST_ACCESS_SADDLE_BAG", + ["TANNED_HORSEHIDE_PANNIER_BLANK", "TANNED_HORSEHIDE_PANNIER_BLANK"] + ); + expect(res2.success).toBe(false); + expect(res2.reason).toContain("warped or lacks durability"); + expect(res2.remainingProvidedLeathers.length).toBe(2); + }); + + it("rejects crafting when insufficient leather is provided and returns provided leathers", () => { + const bench = AncientRunicLeatherSaddleBagBenchEngine.constructBench("leather_02", "OAK_SADDLE_BAG_BENCH"); + + const failRes = AncientRunicLeatherSaddleBagBenchEngine.craftSaddleBag( + bench, + "CARAVAN_HEAVY_DOUBLE_PANNIER", + ["BRASS_RIVETED_BUCKLE_SET"] + ); + + expect(failRes.success).toBe(false); + expect(failRes.reason).toContain("Insufficient saddle bag leather/buckles"); + expect(failRes.remainingProvidedLeathers.length).toBe(1); + expect(bench.currentDurability).toBe(75); + }); + + it("handles gusset torn failure roll consuming durability and leathers", () => { + const bench = AncientRunicLeatherSaddleBagBenchEngine.constructBench("leather_03", "OAK_SADDLE_BAG_BENCH"); // 85% success + + const fail = AncientRunicLeatherSaddleBagBenchEngine.craftSaddleBag( + bench, + "COURIER_FAST_ACCESS_SADDLE_BAG", + ["TANNED_HORSEHIDE_PANNIER_BLANK", "TANNED_HORSEHIDE_PANNIER_BLANK", "TANNED_HORSEHIDE_PANNIER_BLANK"], + 0.95 + ); + + expect(fail.success).toBe(false); + expect(fail.reason).toContain("torn"); + expect(fail.remainingProvidedLeathers?.length).toBe(1); // 3 - 2 = 1 remaining + expect(fail.remainingDurability).toBe(65); // 75 - 10 + }); + + it("gates isFunctional in maintainBench based on DURABILITY_COST_PER_CRAFT threshold and returns clone", () => { + const bench = AncientRunicLeatherSaddleBagBenchEngine.constructBench("leather_04", "OAK_SADDLE_BAG_BENCH"); + bench.currentDurability = 0; + bench.isFunctional = false; + + // Maintain 5 (below 10 required) -> isFunctional remains false + const repLow = AncientRunicLeatherSaddleBagBenchEngine.maintainBench(bench, 5); + expect(repLow.success).toBe(true); + expect(repLow.newDurability).toBe(5); + expect(repLow.isFunctional).toBe(false); + expect(bench.currentDurability).toBe(0); // input unchanged + + // Maintain 10 more on clone -> 15 (>= 10) -> isFunctional becomes true + const repHigh = AncientRunicLeatherSaddleBagBenchEngine.maintainBench(repLow.updatedBench!, 10); + expect(repHigh.success).toBe(true); + expect(repHigh.newDurability).toBe(15); + expect(repHigh.isFunctional).toBe(true); + }); + + it("guards against null inputs and unsupported bench models", () => { + expect(() => AncientRunicLeatherSaddleBagBenchEngine.constructBench("l", "PLASTIC_BENCH" as any)).toThrow( + "Unsupported saddle bag bench type" + ); + + const invalidBench: ActiveSaddleBagBench = { + benchId: "bad", + leatherworkerPlayerId: "p", + benchType: "BENCH" as any, + currentDurability: 50, + maxDurability: 50, + isFunctional: true, + }; + + expect(AncientRunicLeatherSaddleBagBenchEngine.craftSaddleBag(invalidBench, "COURIER_FAST_ACCESS_SADDLE_BAG", ["TANNED_HORSEHIDE_PANNIER_BLANK", "TANNED_HORSEHIDE_PANNIER_BLANK"]).success).toBe(false); + expect(AncientRunicLeatherSaddleBagBenchEngine.craftSaddleBag(null as any, "COURIER_FAST_ACCESS_SADDLE_BAG", []).success).toBe(false); + expect(AncientRunicLeatherSaddleBagBenchEngine.maintainBench(null as any).success).toBe(false); + }); +}); \ No newline at end of file