Skip to content

Commit 9d895c5

Browse files
authored
Feature/no ref/add new test code (#14)
* feat: add jest test package * fix: fixbug sample code * feat: prepare jest config * chore: excule test file in tsconfig * feat: update interface beforeInputHandler * feat: update interface pasteHandler * feat: add handler and test code * feat: update main code support handler method * feat: add test appMaskedInput dom simulation * chore: update path version
1 parent d9ac942 commit 9d895c5

13 files changed

Lines changed: 2975 additions & 82 deletions

jest.config.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
module.exports = {
2+
preset: "ts-jest",
3+
testEnvironment: "node",
4+
testMatch: ["**/*.test.ts"],
5+
transform: {
6+
"^.+\\.ts$": [
7+
"ts-jest",
8+
{
9+
tsconfig: "tsconfig.test.json"
10+
},
11+
],
12+
},
13+
};

package.json

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@krozamdev/masked-password",
3-
"version": "1.0.1",
3+
"version": "1.0.2",
44
"description": "A lightweight and modern utility to mask input fields with password-like characters. This library is specifically designed to prevent password managers from automatically saving values, making it ideal for sensitive data handling. It also provides a method to retrieve the original value of the input. Fully compatible with JavaScript and TypeScript.",
55
"keywords": [
66
"input masking",
@@ -46,13 +46,20 @@
4646
"devDependencies": {
4747
"@rollup/plugin-commonjs": "^28.0.1",
4848
"@rollup/plugin-node-resolve": "^15.3.0",
49+
"@testing-library/dom": "^10.4.0",
50+
"@testing-library/jest-dom": "^6.6.3",
51+
"@types/jest": "^29.5.14",
4952
"@types/node": "^22.10.0",
53+
"jest": "^29.7.0",
54+
"jest-environment-jsdom": "^29.7.0",
5055
"rollup": "^4.27.4",
5156
"rollup-plugin-terser": "^7.0.2",
5257
"terser": "^5.36.0",
58+
"ts-jest": "^29.2.5",
5359
"typescript": "^5.7.2"
5460
},
5561
"scripts": {
56-
"build": "tsc && rollup -c"
62+
"build": "tsc && rollup -c",
63+
"test": "jest"
5764
}
5865
}

samples/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ <h3>Original Value1 : </h3>
2121
<span id="original_value1"></span>
2222
<script>
2323
let show = false;
24+
const inputElement = document.getElementById("passwordInput");
2425
const maskedInput = MaskedPassword.applyMaskedInput(inputElement, {
2526
character: "•",
2627
});
27-
const inputElement = document.getElementById("passwordInput");
2828
const btnToggle = document.getElementById("btn_toggle");
2929
btnToggle.style.textDecoration = "line-through";
3030
document.getElementById("btn").addEventListener("click", function () {

src/applyMaskedInput.test.ts

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
/**
2+
* @jest-environment jsdom
3+
*/
4+
5+
import { applyMaskedInput } from './index';
6+
import { fireEvent } from '@testing-library/dom';
7+
8+
describe("index as applyMaskedInput", () => {
9+
10+
let inputElement: HTMLInputElement;
11+
12+
beforeEach(() => {
13+
document.body.innerHTML = '<input type="text" id="test-input" />';
14+
inputElement = document.getElementById("test-input") as HTMLInputElement;
15+
});
16+
17+
it("should mask input value", () => {
18+
const maskedInput = applyMaskedInput(inputElement, {character : "*"});
19+
20+
// simulate user typing hello
21+
inputElement.dispatchEvent(new InputEvent("beforeinput", { inputType: "insertText", data: "h" }));
22+
inputElement.dispatchEvent(new InputEvent("beforeinput", { inputType: "insertText", data: "e" }));
23+
inputElement.dispatchEvent(new InputEvent("beforeinput", { inputType: "insertText", data: "l" }));
24+
inputElement.dispatchEvent(new InputEvent("beforeinput", { inputType: "insertText", data: "l" }));
25+
inputElement.dispatchEvent(new InputEvent("beforeinput", { inputType: "insertText", data: "o" }));
26+
27+
expect(inputElement.value).toBe("*****");
28+
expect(maskedInput.getOriginalValue()).toBe("hello");
29+
expect(inputElement.selectionStart).toBe(5);
30+
});
31+
32+
it("should mask input value with the specified character", () => {
33+
const maskedInput = applyMaskedInput(inputElement, {character : "*"});
34+
35+
// simulate user typing
36+
inputElement.dispatchEvent(new InputEvent("beforeinput", { inputType: "insertText", data: "h" }));
37+
inputElement.dispatchEvent(new InputEvent("beforeinput", { inputType: "insertText", data: "e" }));
38+
inputElement.dispatchEvent(new InputEvent("beforeinput", { inputType: "insertText", data: "l" }));
39+
inputElement.dispatchEvent(new InputEvent("beforeinput", { inputType: "insertText", data: "l" }));
40+
inputElement.dispatchEvent(new InputEvent("beforeinput", { inputType: "insertText", data: "o" }));
41+
42+
// simulate user typing caret position 5
43+
inputElement.dispatchEvent(new InputEvent("beforeinput", { inputType: "insertText", data: "1" }));
44+
inputElement.dispatchEvent(new InputEvent("beforeinput", { inputType: "insertText", data: "2" }));
45+
inputElement.dispatchEvent(new InputEvent("beforeinput", { inputType: "insertText", data: "3" }));
46+
47+
expect(inputElement.value).toBe("********");
48+
expect(maskedInput.getOriginalValue()).toBe("hello123");
49+
expect(inputElement.selectionStart).toBe(8);
50+
});
51+
52+
it("should mask input value with selected character", () => {
53+
const maskedInput = applyMaskedInput(inputElement, {character : "*"});
54+
55+
// simulate user typing
56+
inputElement.dispatchEvent(new InputEvent("beforeinput", { inputType: "insertText", data: "hello world" }));
57+
58+
// simulate user typing caret position 5 and selected 5 chars
59+
inputElement.setSelectionRange(5,11);
60+
inputElement.dispatchEvent(new InputEvent("beforeinput", { inputType: "insertText", data: "1" }));
61+
inputElement.dispatchEvent(new InputEvent("beforeinput", { inputType: "insertText", data: "2" }));
62+
inputElement.dispatchEvent(new InputEvent("beforeinput", { inputType: "insertText", data: "3" }));
63+
64+
expect(inputElement.value).toBe("********");
65+
expect(maskedInput.getOriginalValue()).toBe("hello123");
66+
expect(inputElement.selectionStart).toBe(8);
67+
});
68+
69+
it("should mask input value with delete character : backspace", () => {
70+
const maskedInput = applyMaskedInput(inputElement, {character : "*"});
71+
72+
// simulate user typing
73+
inputElement.dispatchEvent(new InputEvent("beforeinput", { inputType: "insertText", data: "hello world" }));
74+
75+
// simulate user typing caret position 10
76+
inputElement.setSelectionRange(10,10);
77+
inputElement.dispatchEvent(new InputEvent("beforeinput", { inputType: "deleteContentBackward" }));
78+
79+
expect(inputElement.value).toBe("**********");
80+
expect(maskedInput.getOriginalValue()).toBe("hello word");
81+
expect(inputElement.selectionStart).toBe(9);
82+
});
83+
84+
it("should mask input value with delete selected character : backspace", () => {
85+
const maskedInput = applyMaskedInput(inputElement, {character : "*"});
86+
87+
// simulate user typing
88+
inputElement.dispatchEvent(new InputEvent("beforeinput", { inputType: "insertText", data: "hello world" }));
89+
90+
// simulate user typing caret position 5
91+
inputElement.setSelectionRange(5,10);
92+
inputElement.dispatchEvent(new InputEvent("beforeinput", { inputType: "deleteContentBackward" }));
93+
94+
expect(inputElement.value).toBe("******");
95+
expect(maskedInput.getOriginalValue()).toBe("hellod");
96+
expect(inputElement.selectionStart).toBe(5);
97+
});
98+
99+
it("should mask input value with delete character : delete", () => {
100+
const maskedInput = applyMaskedInput(inputElement, {character : "*"});
101+
102+
// simulate user typing
103+
inputElement.dispatchEvent(new InputEvent("beforeinput", { inputType: "insertText", data: "hello world" }));
104+
105+
// simulate user typing caret position 10
106+
inputElement.setSelectionRange(10,10);
107+
inputElement.dispatchEvent(new InputEvent("beforeinput", { inputType: "deleteContentForward" }));
108+
109+
expect(inputElement.value).toBe("**********");
110+
expect(maskedInput.getOriginalValue()).toBe("hello worl");
111+
expect(inputElement.selectionStart).toBe(10);
112+
});
113+
114+
it("should mask input value with delete selected character : delete", () => {
115+
const maskedInput = applyMaskedInput(inputElement, {character : "*"});
116+
117+
// simulate user typing
118+
inputElement.dispatchEvent(new InputEvent("beforeinput", { inputType: "insertText", data: "hello world" }));
119+
120+
// simulate user typing caret position 5
121+
inputElement.setSelectionRange(5,10);
122+
inputElement.dispatchEvent(new InputEvent("beforeinput", { inputType: "deleteContentForward" }));
123+
124+
expect(inputElement.value).toBe("******");
125+
expect(maskedInput.getOriginalValue()).toBe("hellod");
126+
expect(inputElement.selectionStart).toBe(5);
127+
});
128+
129+
it("should mask input value with paste", () => {
130+
const maskedInput = applyMaskedInput(inputElement, {character : "*"});
131+
132+
// create paste event with clipboard data
133+
fireEvent.paste(inputElement, {
134+
clipboardData: { getData: () => "test123" }
135+
});
136+
137+
expect(inputElement.value).toBe("*******");
138+
expect(maskedInput.getOriginalValue()).toBe("test123");
139+
expect(inputElement.selectionStart).toBe(7);
140+
});
141+
142+
it("should mask input value with paste block character", () => {
143+
const maskedInput = applyMaskedInput(inputElement, {character : "*"});
144+
145+
inputElement.dispatchEvent(new InputEvent("beforeinput", { inputType: "insertText", data: "hello world" }));
146+
inputElement.setSelectionRange(5,10);
147+
148+
// create paste event with clipboard data
149+
fireEvent.paste(inputElement, {
150+
clipboardData: { getData: () => "test123" }
151+
});
152+
153+
expect(inputElement.value).toBe("*************");
154+
expect(maskedInput.getOriginalValue()).toBe("hellotest123d");
155+
expect(inputElement.selectionStart).toBe(12);
156+
});
157+
158+
});
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { beforeInputHandler } from "./beforeInputHandler";
2+
3+
4+
describe("beforeInputHandler", () => {
5+
6+
it("should insert a character at the correct position", () => {
7+
const result = beforeInputHandler({
8+
inputType : "insertText",
9+
originalValue : "abc",
10+
caretPosition : 1,
11+
addedChar : "x"
12+
});
13+
expect(result.newOriginalValue).toBe("axbc");
14+
expect(result.newCaretPosition).toBe(2);
15+
});
16+
17+
it("should handle backspace correctly", () => {
18+
const result = beforeInputHandler({
19+
inputType : "deleteContentBackward",
20+
originalValue : "abc",
21+
caretPosition : 2
22+
});
23+
expect(result.newOriginalValue).toBe("ac");
24+
expect(result.newCaretPosition).toBe(1);
25+
});
26+
27+
it("should handle backspace with selection correctly", () => {
28+
const result = beforeInputHandler({
29+
inputType : "deleteContentBackward",
30+
originalValue : "abcdefghij",
31+
caretPosition : 2,
32+
selectionLength : 4
33+
});
34+
expect(result.newOriginalValue).toBe("abghij");
35+
expect(result.newCaretPosition).toBe(2);
36+
});
37+
38+
it("should handle forward delete correctly", () => {
39+
const result = beforeInputHandler({
40+
inputType : "deleteContentForward",
41+
originalValue : "abc",
42+
caretPosition : 1
43+
});
44+
expect(result.newOriginalValue).toBe("ac");
45+
expect(result.newCaretPosition).toBe(1);
46+
});
47+
48+
it("should handle forward delete with selection first caret position correctly", () => {
49+
const result = beforeInputHandler({
50+
inputType : "deleteContentForward",
51+
originalValue : "abcdefghijkl",
52+
caretPosition : 0,
53+
selectionLength : 5
54+
});
55+
expect(result.newOriginalValue).toBe("fghijkl");
56+
expect(result.newCaretPosition).toBe(0);
57+
});
58+
59+
it("should handle forward delete with selection middle caret position correctly", () => {
60+
const result = beforeInputHandler({
61+
inputType : "deleteContentForward",
62+
originalValue : "abcdefghijkl",
63+
caretPosition : 4,
64+
selectionLength : 5
65+
});
66+
expect(result.newOriginalValue).toBe("abcdjkl");
67+
expect(result.newCaretPosition).toBe(4);
68+
});
69+
70+
it("should handle selection replacement", () => {
71+
const result = beforeInputHandler({
72+
inputType : "insertText",
73+
originalValue : "abcdef",
74+
caretPosition : 2,
75+
selectionLength : 3,
76+
addedChar : "x"
77+
});
78+
expect(result.newOriginalValue).toBe("abxf");
79+
expect(result.newCaretPosition).toBe(3);
80+
});
81+
});

src/handlers/beforeInputHandler.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { beforeInputHandlerConfig, beforeInputHandlerInterface } from "../interfaces/main";
2+
3+
export const beforeInputHandler = (
4+
{
5+
originalValue,
6+
inputType,
7+
caretPosition,
8+
selectionLength = 0,
9+
addedChar = ""
10+
}: beforeInputHandlerConfig
11+
) : beforeInputHandlerInterface => {
12+
13+
let newOriginalValue = originalValue;
14+
15+
if (inputType === "insertText") {
16+
// handle insertion of new character
17+
if (selectionLength > 0) {
18+
// replace selected text
19+
newOriginalValue =
20+
originalValue.slice(0, caretPosition) +
21+
addedChar +
22+
originalValue.slice(caretPosition + selectionLength);
23+
} else {
24+
// insert new character at caret position
25+
newOriginalValue =
26+
originalValue.slice(0, caretPosition) +
27+
addedChar +
28+
originalValue.slice(caretPosition);
29+
}
30+
} else if (inputType === "deleteContentBackward") {
31+
// handle deletion backward (Backspace)
32+
if (selectionLength > 0) {
33+
// remove selected text
34+
newOriginalValue =
35+
originalValue.slice(0, caretPosition) +
36+
originalValue.slice(caretPosition + selectionLength);
37+
} else if (caretPosition > 0) {
38+
// delete character before caret
39+
newOriginalValue =
40+
originalValue.slice(0, caretPosition - 1) +
41+
originalValue.slice(caretPosition);
42+
}
43+
} else if (inputType === "deleteContentForward") {
44+
// handle deletion forward (Delete)
45+
if (selectionLength > 0) {
46+
// remove selected text
47+
newOriginalValue =
48+
originalValue.slice(0, caretPosition) +
49+
originalValue.slice(caretPosition + selectionLength);
50+
} else if (caretPosition < originalValue.length) {
51+
// delete character after caret
52+
newOriginalValue =
53+
originalValue.slice(0, caretPosition) +
54+
originalValue.slice(caretPosition + 1);
55+
}
56+
}
57+
58+
// adjust caret position
59+
let newCaretPosition = caretPosition;
60+
61+
if (inputType === "insertText") {
62+
newCaretPosition = caretPosition + 1;
63+
} else if (inputType === "deleteContentBackward") {
64+
newCaretPosition = selectionLength > 0 ? caretPosition : caretPosition - 1;
65+
} else if (inputType === "deleteContentForward") {
66+
newCaretPosition = caretPosition;
67+
}
68+
69+
// ensure the caret stays within valid bounds
70+
newCaretPosition = Math.max(0, Math.min(newCaretPosition, newOriginalValue.length));
71+
72+
return { newOriginalValue, newCaretPosition };
73+
};

src/handlers/pasteHandler.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { pasteHandler } from './pasteHandler';
2+
3+
describe("pasteHandler", () => {
4+
5+
it("should paste a character at the correct position", () => {
6+
const result = pasteHandler({
7+
originalValue : "abcdfghij",
8+
caretPosition : 2,
9+
pastedText : "123"
10+
});
11+
expect(result.newOriginalValue).toBe("ab123cdfghij");
12+
expect(result.newCaretPosition).toBe(5);
13+
});
14+
15+
it("should paste a character at the correct position with selection", () => {
16+
const result = pasteHandler({
17+
originalValue : "abcdfghij",
18+
caretPosition : 2,
19+
pastedText : "123",
20+
selectionLength : 4
21+
});
22+
expect(result.newOriginalValue).toBe("ab123hij");
23+
expect(result.newCaretPosition).toBe(5);
24+
});
25+
26+
});

0 commit comments

Comments
 (0)