Skip to content

Commit

Permalink
Reworked API, rewrite unit tests and readme
Browse files Browse the repository at this point in the history
  • Loading branch information
royteusink committed Dec 18, 2023
1 parent cb8d4f2 commit f129ffd
Show file tree
Hide file tree
Showing 3 changed files with 159 additions and 73 deletions.
59 changes: 58 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,60 @@
# StackID

A lightweight JavaScript package for managing stacked views like modals. Easily create, push, and pop while ensuring secure closure using unique IDs. Ideal for handling modal interactions, such as closing with specific triggers like 'esc' key, while maintaining a clean and efficient stack structure
A lightweight JavaScript package for managing stacked views like modals. Easily create, push, and pop while ensuring secure closure using unique IDs. Ideal for handling modal interactions, such as closing with specific triggers like 'esc' key, while maintaining a clean and efficient stack structure.

## Installation

You can install the StackID using npm:

```bash
npm install --save stackid
```

## Usage

### Creating and use a stack

To create a new stack, use the `createStack` function:

```ts
import { createStack } from 'stackid';

// Create a stack
const stack = createStack();

const id1 = stack.pushStack(); // Pushes a new identifier (e.g., 0)
const id2 = stack.pushStack(); // Pushes another identifier (e.g., 1)

console.log(stack.onTopStack(id2)); // true

stack.popStack(id2); // Pops the identifier from the stack

console.log(stack.getState()); // [0]
```

### Managing the stack

StackID provides several methods for manipulating the stack:

- `pushStack`: Pushes a new identifier onto the stack and returns the assigned stack id.
- `popStack`: Pops the identifier from the stack if it is on top. Returns the popped stack id or undefined if the stack is empty or the provided identifier is not on top.
- `resetStack`: Resets the stack, removing all identifiers.
- `onTopStack`: Checks if the provided identifier is on top of the stack.
- `getState`: Returns an immutable state of the current stack.

### Customizing Identifier Type

You can customize the type of identifiers used in the stack by providing a type parameter to the `createStack` function. By default, the stack uses `number`:

```ts
const stack = createStack(() => crypto.randomUUID());
pushStack(); // '44f70f2c-2ba6-4c5f-b21b-36a268360e7d
```

## Contributing

Contributions are welcome! If you have any suggestions, improvements, or bug fixes, please open an issue or submit a pull request.

## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
89 changes: 52 additions & 37 deletions src/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,73 +1,88 @@
import { beforeEach, expect, test } from 'vitest';
import { pushStack, popStack, stack, createStack, resetStack } from '.';
import { expect, test } from 'vitest';
import { createStack } from '.';

beforeEach(async () => {
resetStack();
});

test('push to global stack', () => {
const depth1 = pushStack();
expect(stack).toStrictEqual([depth1]);
});

test('pop from the global stack', () => {
const depth1 = pushStack();
expect(stack).toStrictEqual([depth1]);
popStack(depth1);
expect(stack.length).toBe(0);
});

test('reset the global stack', () => {
test('should use a different stack id creation algorith', () => {
const { getState, pushStack } = createStack(() => crypto.randomUUID());
pushStack();
expect(stack.length).toBe(1);
resetStack();
expect(stack.length).toBe(0);
pushStack();
pushStack();
expect(getState().length).toBe(3);
expect(getState()[0]).toBeTypeOf('string');
});

test('global stack meganism', () => {
expect(stack.length).toBe(0);
test('should do stack operations push and pop', () => {
const { getState, pushStack, popStack } = createStack();

expect(getState().length).toBe(0);

const depth1 = pushStack();
expect(stack).toStrictEqual([depth1]);
expect(getState()).toStrictEqual([depth1]);

const depth2 = pushStack();
expect(stack).toStrictEqual([depth1, depth2]);
expect(getState()).toStrictEqual([depth1, depth2]);

popStack(depth1); // depth1 is not on top of the stack, so nothing will change.
expect(stack).toStrictEqual([depth1, depth2]);
expect(getState()).toStrictEqual([depth1, depth2]);

const depth3 = pushStack();
expect(stack).toStrictEqual([depth1, depth2, depth3]);
expect(getState()).toStrictEqual([depth1, depth2, depth3]);

const popDepth2 = popStack(depth1);
expect(popDepth2).toBeUndefined();

const popDepth3 = popStack(depth3);
expect(popDepth3).toBeDefined();

expect(stack).toStrictEqual([depth1, depth2]);
expect(getState()).toStrictEqual([depth1, depth2]);

const depth4 = pushStack();
expect(stack).toStrictEqual([depth1, depth2, depth4]);
expect(getState()).toStrictEqual([depth1, depth2, depth4]);

// Pop all of the stack
popStack(depth4);
popStack(depth2);
popStack(depth1);

expect(stack.length).toBe(0);
expect(getState().length).toBe(0);
});

test('local stack', () => {
test('should allow to use multiple stacks', () => {
const stack1 = createStack();
const { stack: stack2, pushStack: pushStack2 } = createStack();
const stack2 = createStack();

const s1d1 = stack1.pushStack();
expect(stack1.stack).toStrictEqual([s1d1]);
expect(stack1.getState()).toStrictEqual([s1d1]);

const s2d1 = pushStack2();
expect(stack2).toStrictEqual([s2d1]);
const s2d1 = stack2.pushStack();
expect(stack2.getState()).toStrictEqual([s2d1]);

stack1.popStack(s1d1);
expect(stack1.stack.length).toBe(0);
expect(stack1.getState().length).toBe(0);
});

test('should reset the stack', () => {
const { getState, pushStack, resetStack } = createStack();

pushStack();
pushStack();
expect(getState().length).toBe(2);

resetStack();
expect(getState().length).toBe(0);
});

test('should detemine if a id is on top of the stack', () => {
const { onTopStack, pushStack } = createStack();
const first = pushStack();
const second = pushStack();
expect(onTopStack(first)).toBe(false);
expect(onTopStack(second)).toBe(true);;
});

test('should able to force pop from the stack', () => {
const { getState, popStack, pushStack } = createStack();
const first = pushStack();
const second = pushStack();
popStack(first, true);
expect(getState().length).toBe(1);
});
84 changes: 49 additions & 35 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,58 @@
export type StackId = number;
export type Stack = StackId[];

let stackId: StackId = 0;

export const stack: Stack = [];
function genNextId<T>(prev?: T): T {
return (typeof prev === 'number' ? prev + 1 : 0) as T;
}

const _reset = (_stack: Stack) => {
_stack.length = 0;
};
export function createStack<T = number>(nextId?: (prev?: T) => T) {
let curId: T | undefined;
const stack: T[] = [];

const _push = (_stack: Stack) => {
const id = ++stackId;
_stack.push(id);
return id;
};
/**
* Checks if the provided identifier is on top of the stack.
* @param id
*/
function onTopStack(id: T) {
return stack.length > 0 && stack[stack.length - 1] === id;
}

const _pop = (_stack: Stack, id: StackId) => {
if (_stack.length > 0 && _stack[_stack.length - 1] === id) {
return _stack.pop();
/**
* Resets the stack, removing all identifiers.
*/
function resetStack() {
curId = undefined;
stack.length = 0;
}
};

/**
* Push something to the stack.
* @returns The stack id which needs to be stored in order to popStack.
*/
export const pushStack = () => _push(stack);
/**
* Pushes a new identifier onto the stack and returns the assigned stack id.
*/
function pushStack() {
curId = nextId ? nextId(curId) : genNextId(curId);
stack.push(curId);
return curId;
}

/**
* Pop from the stack.
* @returns The stack id that has been popped of.
*/
export const popStack = (id: StackId) => _pop(stack, id);
/**
* Pops the identifier from the stack if it is on top.
* Returns the popped stack id or undefined if the stack is empty or the provided identifier is not on top.
* @param id
* @param force
*/
function popStack(id: T, force?: boolean) {
if (onTopStack(id) || force) return stack.pop();
}

export const resetStack = () => _reset(stack);
/**
* Get a immutable state of the current stack.
*/
function getState(): T[] {
return [...stack];
}

export function createStack() {
const localStack: Stack = [];
const pushStack = () => _push(localStack);
const popStack = (id: StackId) => _pop(localStack, id);
const resetStack = () => _reset(localStack);
return { stack: localStack, pushStack, popStack, resetStack };
return {
getState,
pushStack,
popStack,
resetStack,
onTopStack,
};
}

0 comments on commit f129ffd

Please sign in to comment.