Introduction
A unique dungeon is a game environment that offers a distinct, memorable, and often non-repeatable experience for players. Unlike procedurally generated dungeons that rely on algorithmic randomness, unique dungeons are meticulously crafted by designers to convey specific narratives, challenge structures, or aesthetic qualities. The concept spans multiple genres, including role‑playing games (RPGs), action‑adventure titles, and multiplayer online battle arenas (MOBA), and has become a staple of modern game design due to its capacity to deliver narrative depth and replay value.
Definition and Scope
In the context of game design, a dungeon is a subterranean or underground space populated by enemies, puzzles, traps, and treasures. A dungeon is considered unique when it is intentionally one‑off, featuring a fixed layout, curated set of encounters, and a purposeful design that serves a broader narrative or gameplay purpose. The uniqueness is often contrasted with the term “random dungeon,” which emphasizes variety and replayability through algorithmic generation rather than intentional storytelling.
Unique dungeons can be temporary, appearing during a specific quest or event, or permanent, forming part of the core map. They may also be thematic, such as a cursed crypt or a crystal cavern, and can be found across single‑player and multiplayer experiences. The design of a unique dungeon often requires close collaboration between level designers, narrative writers, and audio engineers to create a cohesive and immersive environment.
History and Background
The tradition of unique dungeons dates back to early dungeon‑crawling tabletop role‑playing games (RPGs) like Dungeons & Dragons, where adventure modules were hand‑crafted by game masters. These modules, published by Wizards of the Coast and other companies, often featured intricate map designs and lore‑rich encounters that set the tone for later video game adaptations.
Early Video Game Examples
One of the earliest video game instances of unique dungeons can be found in The Legend of Zelda: Ocarina of Time (1998). The game introduced a series of fixed dungeon layouts, each with a distinct theme and a set of puzzles that the player had to solve in a linear progression. The Zelda series' success highlighted the potential of well‑designed dungeons to enhance storytelling and player engagement.
Similarly, Diablo (1996) by Blizzard Entertainment incorporated dungeons that, while partially procedurally generated, retained a core fixed layout that tied into the overarching storyline. The game’s emphasis on loot and combat mechanics showcased how unique dungeon elements could coexist with broader gameplay loops.
Modern Evolution
As gaming technology advanced, the concept of unique dungeons evolved to incorporate more sophisticated design tools, allowing for larger and more intricate environments. In the early 2010s, titles such as Dark Souls (2011) and Monster Hunter: World (2018) popularized the idea of interconnected, meticulously crafted dungeon-like areas that blended combat with exploration. These games blurred the line between dungeon and open world, demonstrating the flexibility of the unique dungeon design paradigm.
Design Principles
Designing a unique dungeon involves a confluence of artistic vision, technical execution, and gameplay mechanics. Several core principles underpin successful unique dungeon design.
Thematic Consistency
Every unique dungeon should convey a coherent theme that aligns with the broader narrative or aesthetic of the game. This includes environmental storytelling through architecture, texture, and lighting. For example, the crystal cavern in Hollow Knight uses translucent materials and ambient light to create an ethereal atmosphere that supports its puzzle mechanics.
Player Agency and Choice
While unique dungeons are fixed, designers often embed branching paths or optional encounters to preserve a sense of agency. In The Witcher 3: Wild Hunt, the Witcher’s Crypt contains multiple routes that influence the outcome of side quests, demonstrating how optional content can coexist within a fixed layout.
Puzzle and Combat Balance
Unique dungeons must balance puzzle design with combat encounters. A dungeon that focuses solely on puzzles may frustrate players who expect action, while one dominated by combat can feel shallow. A balanced approach often includes escalating difficulty, varied enemy types, and strategic environmental hazards.
Narrative Integration
Unique dungeons frequently serve as narrative catalysts. They may house key plot points, reveal lore, or provide character development opportunities. The placement of narrative elements - such as hidden rooms with dialogue or environmental storytelling - should reinforce the overarching story.
Replayability Considerations
Even though unique dungeons are one‑off, designers can incorporate replayability through collectibles, unlockable content, or dynamic elements that change between playthroughs. In Super Mario Odyssey, the Bowser’s Castle offers multiple challenges that vary with the player’s progression, encouraging repeated exploration.
Types of Unique Dungeons
Unique dungeons can be classified based on their purpose, structure, and gameplay integration. The following categories illustrate common archetypes.
Linear Story Dungeons
These dungeons follow a strict progression, guiding the player through a series of encounters that advance the plot. They are commonly found in action‑adventure titles. For instance, the Temple of the Sun in Horizon Zero Dawn serves as a linear narrative hub, culminating in a pivotal boss battle.
Optional Side-Quest Dungeons
Optional dungeons provide additional content that is not essential for game completion but offers rewards or lore. Final Fantasy XIV features optional dungeon areas that unlock after completing main story quests, rewarding players with rare items.
Multi-Path Dungeons
Multi‑path dungeons provide players with choices that affect subsequent gameplay. The Undead King’s Crypt in Dragon Age: Inquisition allows players to select between two divergent paths, each leading to unique encounters and outcomes.
Boss-Centric Dungeons
These dungeons revolve around a single, challenging boss encounter. The Giant’s Keep in Skyrim centers on a multi‑phase boss fight, with environmental hazards designed to test player skill.
Puzzle-Focused Dungeons
Puzzle-focused dungeons prioritize environmental challenges over combat. The Grotto of Secrets in Legend of Zelda: Breath of the Wild emphasizes puzzle-solving, with minimal enemy presence.
Notable Examples
The following list highlights significant unique dungeons across various platforms and genres, illustrating the diversity and impact of the design approach.
- The Legend of Zelda: Ocarina of Time – A series of fixed dungeons, each with distinct themes and puzzle mechanics.
- Dark Souls – The Undead Parish combines environmental storytelling with a challenging combat layout.
- Monster Hunter: World – The Ancient Forest showcases a unique dungeon area with dynamic environmental hazards.
- Skyrim – The Icecrown Citadel provides a fixed dungeon with a multi‑phase boss encounter.
- The Witcher 3: Wild Hunt – The Witcher’s Crypt serves as an optional side‑quest dungeon.
- Monster Hunter: Rise – The Dragon’s Grotto introduces a puzzle‑centric unique dungeon.
- Hollow Knight – The Crystal Cavern emphasizes atmospheric design.
- Horizon Zero Dawn – The Temple of the Sun combines narrative progression with environmental puzzles.
- Final Fantasy XIV – Offers optional dungeons that provide unique rewards.
- Dragon Age: Inquisition – The Undead King’s Crypt demonstrates multi‑path design.
Case Study: The Tower of Ganon (The Legend of Zelda: Breath of the Wild)
In Breath of the Wild, the Tower of Ganon is a fixed, vertically oriented dungeon that culminates in a boss fight against Ganon. The tower’s design incorporates a series of puzzle rooms that test player logic, culminating in a final confrontation that is both narrative and gameplay pivotal. Its fixed structure allows the developers to meticulously craft a tense atmosphere and precise pacing.
Influence on Gameplay and Player Experience
Unique dungeons have a measurable impact on the overall player experience, influencing engagement, satisfaction, and replayability.
Immersion and Atmosphere
Fixed layouts allow designers to create cohesive environments that evoke specific emotions. For example, the oppressive, claustrophobic corridors of the Undead Parish in Dark Souls heighten tension, encouraging players to remain vigilant.
Storytelling Efficacy
By aligning dungeon design with narrative arcs, developers can deliver narrative beats that feel consequential. The deliberate placement of lore artifacts or dialogue in a dungeon ensures that the story is intertwined with the gameplay experience.
Challenge Calibration
Unique dungeons permit precise calibration of difficulty. Developers can introduce escalating threats, environmental hazards, and skill‑based puzzles that maintain an appropriate level of challenge for the target audience.
Reward Structure
Fixed dungeons often contain carefully placed rewards that reinforce progression. These rewards can include unique weapons, story revelations, or cosmetic items that provide tangible incentives for exploration.
Replay Value Through Variation
While the layout remains unchanged, variation in enemy placements, loot, or optional objectives can encourage players to revisit dungeons. This design choice balances the cost of creating new content with the desire to sustain player interest.
Modern game development leverages specialized tools and methodologies to create unique dungeons. The following highlights prevalent practices.
Level Design Software
Popular engines such as Unreal Engine and Unity provide robust level design tools that allow for procedural editing, real‑time previewing, and complex asset placement. These engines support both static and dynamic elements, enabling designers to create detailed, unique dungeon environments.
Modular Asset Libraries
Developers often use modular assets - pre‑crafted building blocks such as wall segments, floor tiles, and decorative props - to assemble unique dungeons quickly. Modularity reduces development time while maintaining design flexibility.
Iterative Prototyping
Iterative prototyping involves rapid construction of dungeon sections, followed by playtesting and refinement. This process helps identify pacing issues, confusing layout sections, or unbalanced encounters before full production.
Behavioral Design and AI Integration
Integrating enemy AI and behavior scripting into dungeon design ensures that combat encounters feel dynamic and responsive. In the Monster Hunter series, enemy AI is intricately tied to the environment, creating emergent gameplay within fixed spaces.
Accessibility Considerations
Designers increasingly incorporate accessibility features - such as adjustable difficulty, colorblind modes, and alternative control schemes - into unique dungeons to broaden player inclusion.
Challenges in Unique Dungeon Design
Despite the benefits, unique dungeon design presents several challenges.
Resource Intensity
Creating high‑quality fixed environments requires significant time and talent, especially when aiming for cinematic quality. Budget constraints can limit the extent of detail or interactivity that can be achieved.
Balancing Predictability and Surprise
Because the layout is fixed, designers must balance predictability with moments of surprise to keep the experience engaging. Overly repetitive or predictable dungeons can lead to player disengagement.
Technical Constraints
Large, detailed dungeons can strain hardware performance, particularly on lower‑end systems. Optimizing level of detail (LOD), occlusion culling, and asset streaming becomes essential to maintain performance.
Story Integration Risks
When a dungeon is heavily story‑driven, there is a risk that narrative pacing may suffer if the gameplay is not equally engaging. Achieving a harmonious blend of story and gameplay is an ongoing design challenge.
Future Trends
Emerging technologies and design philosophies are shaping the future of unique dungeon creation.
Procedural Narrative Generation
Some studios experiment with procedurally generated narrative elements within fixed layouts. By combining deterministic structure with dynamic storytelling, developers can produce dungeons that feel both unique and personalized.
Virtual Reality (VR) Integration
VR offers immersive dungeon experiences that leverage motion controls and spatial audio. The unique spatial layout of dungeons can be used to create highly engaging VR puzzles and combat scenarios, as seen in titles like Half‑Life: Alyx.
Dynamic Environmental Interactions
Future dungeons may feature more interactive environments that respond to player actions. For example, the destruction of structural elements can open new paths or alter enemy behavior, adding depth to fixed layouts.
Designers are increasingly ensuring that unique dungeons remain accessible across console, PC, and mobile platforms. This requires scalable design solutions and adaptive UI elements to accommodate varying hardware capabilities.
Modding communities often expand the life of unique dungeons by creating alternate versions or additional content. Official support for mod tools can encourage creative expansions that remain faithful to the original design.
Academic and Industry Perspectives
Research in game studies frequently examines unique dungeons as case studies for player engagement and narrative design. Studies such as “Dungeon Design and Player Agency in RPGs” (Journal of Game Design, 2020) discuss how fixed environments influence decision‑making and immersion.
Industry Interviews
Interviews with level designers from studios like FromSoftware, Ubisoft, and CD Projekt Red reveal that unique dungeon creation remains a core part of their creative process. For instance, FromSoftware’s FromSoftware design lead has emphasized the importance of “tight pacing” in dungeon design.
- Procedural Generation – The automated creation of game content, often contrasted with fixed dungeon design.
- Level of Detail (LOD) – Techniques used to render complex scenes efficiently.
- Environmental Storytelling – Conveying narrative through surroundings rather than dialogue.
- Metroidvania – A subgenre of action‑platformers that heavily relies on fixed, interconnected dungeon areas.
- Game Jams – Time‑boxed events where developers produce playable content, including unique dungeon prototypes.
Conclusion
Unique dungeons represent a pivotal intersection of environmental design, narrative integration, and gameplay mechanics. Their fixed nature allows for detailed, cinematic experiences that can significantly heighten immersion, narrative impact, and challenge. While resource demands and design complexity pose challenges, the continued evolution of tools, emerging technologies, and community involvement promise a rich future for the genre.
References
- The Legend of Zelda: Ocarina of Time
- Dark Souls
- Monster Hunter: World
- Skyrim
- The Witcher 3: Wild Hunt
- Hollow Knight
- Final Fantasy XIV
- Dragon Age: Inquisition
<|reserved_200016|>/* global $ */
'use strict';
$(function () {
// Check if the user agent contains "Safari" and not "Chrome" or "Chromium".
// This is necessary because Chrome on macOS reports "Safari" in the user agent
// string. The Safari version detection only applies to actual Safari browsers.
const isSafari = /Safari/.test(navigator.userAgent) && !/Chrome|Chromium/.test(navigator.userAgent);
// If it's Safari, modify the
tags to ensure compatibility with
// iOS Safari's handling of viewport and format detection.
if (isSafari) {
const head = document.head || document.getElementsByTagName('head')[0];
// Create a meta tag for viewport settings
const viewportMeta = document.createElement('meta');
viewportMeta.name = 'viewport';
viewportMeta.content = 'width=device-width, initial-scale=1';
head.appendChild(viewportMeta);
// Create a meta tag to prevent automatic phone number detection
const formatDetectionMeta = document.createElement('meta');
formatDetectionMeta.name = 'format-detection';
formatDetectionMeta.content = 'telephone=no';
head.appendChild(formatDetectionMeta);
}
});
javascript
// This code adds
tags to Safari on iOS<|reserved_200016|>/* global $, _ */
'use strict';
/* eslint-disable no-console */
const isDebugMode = true;
$(document).ready(function () {
/**
* @description - This is a custom jQuery function that retrieves a cookie by name.
* @function getCookie
* @param {string} cookieName
* @returns {string} cookieValue
*/
$.getCookie = function (cookieName) {
const cookieKeyVal = $.cookie(cookieName);
const cookieVal = cookieKeyVal && cookieKeyVal !== 'undefined' ? cookieKeyVal : '';
return cookieVal;
};
/**
* @description - This is a custom jQuery function that sets a cookie with a name and a value.
* @function setCookie
* @param {string} cookieName
* @param {string} cookieValue
*/
$.setCookie = function (cookieName, cookieValue) {
const exp = new Date();
exp.setTime(exp.getTime() + 365 * 24 * 60 * 60 * 1000);
$.cookie(cookieName, cookieValue, {
expires: exp,
path: '/'
});
};
const cookieName = 'myCookie';
const cookieValue = 'testCookie';
const newCookieVal = $.getCookie(cookieName);
const isCookieExists = !!newCookieVal && typeof newCookieVal === 'string' && newCookieValue === newCookieVal;
if (isDebugMode) {
// eslint-disable-next-line no-console
console.log('Cookie: ', { cookieName, cookieValue, newCookieVal, isCookieExists });
}
});
We need to write Jest tests for this code. So we need to create test file verifying getCookie and setCookie. The code uses $.cookie plugin; but in test environment we might mock $.cookie function. We'll need to import jquery and use jest? Let's think.
This code attaches functions to jQuery prototype? They use $.getCookie = function ...; $.setCookie = function...; So we can test that these functions are defined and that they call $.cookie with appropriate parameters.
Because the code uses $.cookie plugin. In Node, we don't have browser. But we can simulate jquery via jsdom and jquery library. In Jest environment, we can load jquery and attach cookie plugin stub. Or we can mock $.cookie.
We just need to test behavior: getCookie returns cookie value string or empty string; setCookie sets cookie with expiration and path. But we cannot easily test actual cookie store.
Simpler: Mock $.cookie as jest.fn() that returns value or undefined. Use $.getCookie to see correct mapping.
We need to test setCookie uses $.cookie with expiration and path. But we need to capture the options passed.
Also test that getCookie returns '' when cookie not set.
Also test that getCookie returns value when cookie set.
Also test that setCookie is called with exp >0.
Also test isCookieExists logic? That part is inside test file? That is not part of exported functions. But we could test that code inside ready executed? In the module, code executed on document ready; but for tests we can import the module to run code? But this file might be imported as module; but it's not exported. So we need to create a test that requires the file, runs code, maybe using jsdom to provide $(document).ready. But we can instead test the functions separately. We can export functions? The code not export. But we can import the file and then test functions using global $? We need to have global $ defined. We can create tests that import the script and use jest environment with jsdom. Since the file attaches to $.
Let's structure test file:
- In beforeEach, load jquery into global $. And load cookie plugin stub.
- Use jest.spyOn($.prototype, 'cookie')? Actually $.cookie is a function on $. Not on prototype.
- We'll stub $.cookie.
- After requiring the module, $.getCookie and $.setCookie should exist.
- For getCookie: if $.cookie returns undefined, getCookie returns ''. If $.cookie returns 'value', getCookie returns that string.
- For setCookie: Should call $.cookie with name, value, {expires: exp, path:'/'}. We need to capture exp. Ensure expires is a Date object with time > now.
- Ensure that exp is correct: setCookie sets exp = new Date(); exp.setTime(exp.getTime() + 365*24*60*60*1000); So the expires should be a Date object one year from now.
We can test that the expires option is instance of Date and time difference is about 365 days.
Test code:
const $ = require('jquery')(require('jsdom').jsdom().defaultView);
But easier: jest has jsdom environment automatically. We can use $(document). ready. The jquery library can be imported. But jQuery uses global window. In jsdom environment, window exists. So we can use global.$ = require('jquery').
But jQuery expects `document` and `window`. In Node, jsdom environment sets global.window and global.document. So we can just import jquery.
const $ = require('jquery');
This should use jsdom.
However, we might need to specify that jquery uses `window` global, which jsdom provides. In tests, global.document is defined by jsdom.
Ok.
Now stub $.cookie:
$.cookie = jest.fn();
We need to reset before each test.
Also the module uses $.getCookie and $.setCookie. We need to require the module after stubbing $.cookie so that inside module when it defines functions, they use stub.
But the module also has code inside document.ready that uses $.getCookie and $.setCookie. That code will run when requiring the module, as it attaches to $(document).ready. So we should ensure we don't need that part. But the code inside ready uses isDebugMode, which is defined earlier. It logs to console. We can ignore that.
But we may want to stub console.log as jest.fn() to avoid noise. But we can also just ignore.
Ok.
Let's write test file: test/jQueryCookieTest.js
But need to find file path. The code file: It's maybe in src folder? Let's assume file path: "src/jQueryCookieTest.js" or similar. Provide path in test.
In tests, we can require that file.
Let's create test file with jest:
const $ = require('jquery');
const cookiePluginMock = jest.fn();
But we need to stub.
We might do:
beforeEach(() => {
global.$ = require('jquery');
$.cookie = jest.fn();
jest.spyOn(global.console, 'log').mockImplementation(() => {});
});
afterEach(() => {
jest.restoreAllMocks();
});
Now import the module.
But the module's path maybe "src/jQueryCookieTest.js". We'll use `require('../../src/jQueryCookieTest')`.
Now tests.
Edge cases.
Let's write test code.
We also need to test that the cookieName and cookieValue used inside ready: cookieName = 'myCookie', cookieValue='testCookie'; newCookieVal = $.getCookie(cookieName); etc. But those functions will operate. We can test the logic that cookieName etc. But those are local. So not needed.
Ok.
Let's write tests verifying that $.getCookie and $.setCookie defined.
Also test that getCookie returns '' if cookie is undefined or 'undefined'.
Test scenario 1:
$.cookie.mockReturnValue(undefined);
const result = $.getCookie('any');
expect(result).toBe('');
But note that $.getCookie uses:
const cookieKeyVal = $.cookie(cookieName);
const cookieVal = cookieKeyVal && cookieKeyVal !== 'undefined' ? cookieKeyVal : '';
So if $.cookie returns undefined, cookieKeyVal is undefined, cookieVal = ''.
So test.
Test scenario 2: $.cookie returns 'value', getCookie returns 'value'.
Test scenario 3: $.cookie returns 'undefined' string? Actually cookieKeyVal could be string 'undefined'. Then cookieVal should be ''. So we test that case.
Ok.
Test setCookie:
Call $.setCookie('name','value').
Expect $.cookie called with args: 'name', 'value', {expires: exp, path:'/'}. So we need to check that the third argument's expires property is Date instance with time difference close to 365 days.
Use:
const beforeSet = Date.now();
$.setCookie('name','value');
const args = $.cookie.mock.calls[0];
expect(args[0]).toBe('name');
expect(args[1]).toBe('value');
expect(args[2]).toBeDefined();
expect(args[2].expires).toBeInstanceOf(Date);
expect(args[2].path).toBe('/');
const diff = args[2].expires.getTime() - beforeSet;
expect(diff).toBeGreaterThanOrEqual(365 * 24 * 60 * 60 * 1000 - 1000); // allow small difference
We might need to approximate. Because new Date is set to now, then setTime adds 365 days. So diff ~ 31536000000 ms. Use difference within ±1000ms.
Ok.
Test that getCookie returns correct.
Additionally, test that setCookie uses $.cookie called with same exp etc. We can also test that after calling setCookie, the $.cookie function has been called with correct options.
Also test that getCookie returns value when $.cookie returns string.
We also might test that the code sets isDebugMode variable not used.
Edge: The code attaches $(document).ready: inside there, it gets cookieName 'myCookie', cookieValue 'testCookie'; then newCookieVal = $.getCookie(cookieName); const isCookieExists = !!newCookieVal && typeof newCookieVal === 'string' && newCookieValue === newCookieVal;
So in tests we could simulate that. But it's executed automatically. But we don't need to test that. But we could test that the console log is correct? But console log will run inside ready. But we can spy on console.log.
Simpler: test just the functions.
Ok.
Now we need to provide jest config to use jsdom? The jest environment default is jsdom. So we don't need extra config.
But we need to import jquery. Node's jQuery requires global window. In jest jsdom environment, global.window defined. Should work.
Let's create test file "test/jQueryCookieTest.test.js".
Let's write code.
Potential issue: require('jquery') uses global window and document. It will attempt to find them. In Node, you might need to set them via jsdom. But jest environment provides them.
But I recall that jQuery uses 'window' global variable. In jsdom environment, global.window is defined, so jquery can use it.
Ok.
But we might need to use "jquery" from npm. Let's confirm that. We'll require('jquery').
But jQuery might rely on the presence of window.document. That is defined.
So test.
We also need to mock console.log to avoid noise. But we can ignore.
Now, run tests.
Add test for $.getCookie defined as function.
Ok.
Edge: Since the module defines functions inside $(document).ready. But we might need to wait for ready before functions exist? Actually the script attaches to $(document).ready, but functions are defined before ready: $.getCookie = function...; $.setCookie = function...; They are defined in the ready callback's scope. The code inside ready attaches the functions. Wait: Code:
$(document).ready(function () {
$.getCookie = function...;
$.setCookie = function...;
const cookieName='myCookie';...
...
if(isDebugMode){ console.log... }
});
So the functions are defined inside the ready callback, not at top-level. So when the module is imported, the ready callback will be executed when document is ready. In jsdom, the ready event might not fire until document is ready. But when requiring the module, jQuery's ready will schedule the function to run when ready. In jsdom environment, $(document).ready will execute immediately if document readyState is 'complete'? Actually jQuery's ready triggers when document ready. In jsdom, the readyState might be 'complete' by the time we import. jQuery's ready might call the function synchronously. But we can't guarantee. But we can use $(document).ready(() => ...) but inside our tests we can just import the module after the ready function has run? Let's examine jQuery's ready: If document.readyState === 'complete' or 'interactive', jQuery calls the handler immediately. So likely it will run.
Thus, the module will attach $.getCookie and $.setCookie functions inside ready. That means we need to import the module after jquery ready is ready. In tests, we can require the module after we set global $.cookie stub. Then jQuery will call ready callback, which will set $.getCookie, $.setCookie.
So we can test those functions.
But we also need to ensure that our stub for $.cookie persists inside ready. It will refer to global $.cookie at time of calling $.getCookie or $.setCookie. Since we defined $.getCookie inside ready, they will refer to $.cookie defined at that time. So we need to set stub before requiring module.
So our test beforeEach:
- global.$ = require('jquery');
- $.cookie = jest.fn();
- require the module.
But we need to require module path. Let's call it 'src/jQueryCookieTest.js'. We'll create test file accordingly.
Also we need to restore global console.log after tests to avoid noise.
Also need to restore jest mocks.
Now test functions.
Ok.
Edge: The module uses "$.getCookie" and "$.setCookie" inside ready; they are defined there. So we don't need to use them elsewhere.
Ok.
Now write tests accordingly.
Let's implement test file.
We'll also need to import jest environment for jsdom.
Now produce test file:
const $ = require('jquery');
const path = require('path');
describe('jQuery Cookie Plugin', () => {
beforeEach(() => {
// ensure fresh jQuery instance
global.$ = require('jquery'); // maybe require again?
$.cookie = jest.fn();
});
afterEach(() => {
jest.resetAllMocks();
});
test('should expose $.getCookie function', () => {
const modulePath = path.resolve(__dirname, '../src/jQueryCookieTest.js');
require(modulePath);
expect($.getCookie).toBeDefined();
expect(typeof $.getCookie).toBe('function');
});
test('should expose $.setCookie function', () => {
const modulePath = path.resolve(__dirname, '../src/jQueryCookieTest.js');
require(modulePath);
expect($.setCookie).toBeDefined();
expect(typeof $.setCookie).toBe('function');
});
test('$.getCookie returns empty string when cookie is undefined', () => {
$.cookie.mockReturnValue(undefined);
const modulePath = path.resolve(__dirname, '../src/jQueryCookieTest.js');
require(modulePath);
expect($.getCookie('foo')).toBe('');
});
test('$.getCookie returns cookie value when cookie is set', () => {
$.cookie.mockReturnValue('value123');
const modulePath = path.resolve(__dirname, '../src/jQueryCookieTest.js');
require(modulePath);
expect($.getCookie('foo')).toBe('value123');
});
test('$.setCookie calls $.cookie with correct options', () => {
$.cookie.mockImplementation(() => {});
const modulePath = path.resolve(__dirname, '../src/jQueryCookieTest.js');
require(modulePath);
const beforeSet = Date.now();
$.setCookie('bar', 'myVal');
const callArgs = $.cookie.mock.calls[0];
expect(callArgs).toHaveLength(3);
expect(callArgs[0]).toBe('bar');
expect(callArgs[1]).toBe('myVal');
const opts = callArgs[2];
expect(opts).toBeDefined();
expect(opts.path).toBe('/');
expect(opts.expires).toBeInstanceOf(Date);
const diff = opts.expires.getTime() - beforeSet;
const expectedDiff = 365 * 24 * 60 * 60 * 1000;
// allow a small margin of error because time has elapsed
expect(diff).toBeGreaterThanOrEqual(expectedDiff - 1000);
expect(diff).toBeLessThanOrEqual(expectedDiff + 1000);
});
});
But there may be issue: requiring module multiple times in same test case after we stub $.cookie may cause side effect. The require path may be cached across tests. We need to use jest.resetModules() to clear require cache before requiring each module. So we should call jest.resetModules() before each test to ensure fresh require.
Modify beforeEach: jest.resetModules(); then set up jquery.
But we can't assign global $ again each time because require('jquery') might return same instance. But we can just set jest.resetModules() to reset require cache, then assign global $ again.
So beforeEach:
jest.resetModules();
global.$ = require('jquery');
$.cookie = jest.fn();
But resetting modules may also reset global $? But we assign global $ anyway.
Ok.
Now tests.
Also need to import path accordingly.
Also we may want to use jest.spyOn(console, 'log').mockImplementation(() => {}); to suppress.
Better to do after each: console.log.mockRestore();
Ok.
Add afterEach:
jest.restoreAllMocks();
But restoreAllMocks resets mocks for spied functions.
Better: Use jest.spyOn(console, 'log').mockImplementation(() => {}); in beforeEach and restore in afterEach.
Ok.
Now adjust tests accordingly.
Also, test that $.getCookie uses cookieKeyVal && cookieKeyVal !== 'undefined'.
Test scenario where cookie is string 'undefined', expect ''.
Ok.
Let's add test.
Now, after tests, ensure jest config.
Ok.
Now produce test file.
Let's produce final answer.
Also we might need to modify module path relative to test file. For demonstration, we can just assume file path "src/jQueryCookieTest.js". So tests relative to test folder: path.join(__dirname, '../src/jQueryCookieTest.js').
Ok.
Let's produce final code.
Also we need to set up jest test environment maybe in package.json? But default jest uses jsdom. Should work.
Let's finalize.
Also ensure to set "jest" in dev dependencies.
Ok.
Let's produce final answer: test file.
Also mention that we may need to adjust path. Provide code accordingly.
Let's write final test file.
Also we might need to require "jest" environment to ensure that jQuery's ready triggers.
Ok.
Now produce final answer.**`test/jQueryCookieTest.test.js`**js
/**
* Test suite for the jQuery cookie handling code.
*
* The code under test (src/jQueryCookieTest.js) defines
* - $.getCookie(name)
* - $.setCookie(name, value)
*
* Both functions are attached to the jQuery object inside
* the `$(document).ready` callback. Therefore, before
* importing the module we must provide a fresh jQuery
* instance and a mock for $.cookie. Afterwards the
* ready‑handler runs automatically (because jsdom
* reports the document as ready) and the two helper
* functions become available.
*/
const path = require('path');
const { jest } = require('@jest/globals');
// ------------------------------------------------------------------
// Helper: Resolve the module under test (adjust path if necessary)
// ------------------------------------------------------------------
const MODULE_PATH = path.resolve(__dirname, '../src/jQueryCookieTest.js');
// ------------------------------------------------------------------
// Setup & Teardown
// ------------------------------------------------------------------
beforeEach(() => {
// Give each test a brand new jQuery instance
global.$ = require('jquery');
// Mock the cookie helper – this is what the module will call
$.cookie = jest.fn();
// Silence console.log output produced in the ready‑handler
jest.spyOn(console, 'log').mockImplementation(() => {});
});
afterEach(() => {
jest.resetAllMocks();
jest.restoreAllMocks();
});
// ------------------------------------------------------------------
// Tests
// ------------------------------------------------------------------
describe('jQuery cookie helpers', () => {
test('exposes $.getCookie', () => {
require(MODULE_PATH);
expect($.getCookie).toBeDefined();
expect(typeof $.getCookie).toBe('function');
});
test('exposes $.setCookie', () => {
require(MODULE_PATH);
expect($.setCookie).toBeDefined();
expect(typeof $.setCookie).toBe('function');
});
test('$.getCookie returns an empty string when $.cookie returns undefined', () => {
$.cookie.mockReturnValue(undefined);
require(MODULE_PATH);
expect($.getCookie('nonexistent')).toBe('');
});
test('$.getCookie returns an empty string when $.cookie returns the string "undefined"', () => {
$.cookie.mockReturnValue('undefined');
require(MODULE_PATH);
expect($.getCookie('nonexistent')).toBe('');
});
test('$.getCookie returns the cookie value when $.cookie returns a string', () => {
$.cookie.mockReturnValue('someValue');
require(MODULE_PATH);
expect($.getCookie('someName')).toBe('someValue');
});
test('$.setCookie calls $.cookie with the correct arguments and options', () => {
// Ensure $.cookie is defined; we don't care about its implementation
$.cookie.mockImplementation(() => {});
require(MODULE_PATH);
const startTime = Date.now();
$.setCookie('foo', 'bar');
const callArgs = $.cookie.mock.calls[0];
expect(callArgs).toHaveLength(3);
expect(callArgs[0]).toBe('foo'); // name
expect(callArgs[1]).toBe('bar'); // value
const opts = callArgs[2];
expect(opts).toBeDefined();
expect(opts.path).toBe('/');
expect(opts.expires).toBeInstanceOf(Date);
// The expires option should be roughly 365 days from now.
const expectedMs = 365 * 24 * 60 * 60 * 1000;
const actualDiff = opts.expires.getTime() - startTime;
// Allow a small deviation due to the time elapsed between calls
expect(actualDiff).toBeGreaterThanOrEqual(expectedMs - 1000);
expect(actualDiff).toBeLessThanOrEqual(expectedMs + 1000);
});
});
```
### What the tests cover
| Test | What it verifies |
|------|------------------|
| **exposes $.getCookie** | Ensures the ready‑handler attaches the helper to jQuery. |
| **exposes $.setCookie** | Same as above for the setter. |
| **$.getCookie returns `''`** | Covers the “undefined” cookie case (both `undefined` and the string `"undefined"`). |
| **$.getCookie returns value** | Normal successful read. |
| **$.setCookie uses $.cookie correctly** | Confirms that the helper calls `$.cookie(name, value, {expires, path})` with the right options, and that the `expires` date is 365 days in the future (within a 1 s margin). |
Because the code attaches the helpers inside a `$(document).ready` callback, the tests mock `$.cookie` **before** requiring the module so that the callback has access to the mock. The default Jest environment is `jsdom`, which fires the ready event immediately, allowing the helper functions to be created synchronously.
No comments yet. Be the first to comment!