-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: implement checkbox component (#9)
* feat: implement Checkbox component for checkbox management This component encapsulates functionality for creating, removing, and changing the state of checkboxes. It utilizes strict mode and imports necessary functions from respective modules for checkbox creation, removal, and state change. The constructor ensures that the provided configuration is verified before initialization. * feat: implement checkbox creation function This function creates a checkbox element based on the provided configuration. It utilizes strict mode and imports necessary functions for verifying the configuration and setting attributes. The checkbox element includes an icon and attributes such as ID, class, state, group name, and target ID as specified in the configuration. * feat: implement checkbox state management function This function handles the change of checkbox state based on user interaction. It utilizes strict mode and imports necessary functions for verifying the configuration. The function toggles the checkbox state, updates the checkbox icon accordingly, and manages the targets associated with the checkbox. * feat: implement checkbox removal function This function removes a checkbox element from the DOM based on the provided configuration. It utilizes strict mode and imports necessary functions for verifying the configuration. Additionally, it removes associated items from local storage to maintain data consistency. * refactor: update import paths for module resolution Updated import paths in component files to use module resolution aliases defined in jsconfig.json. This change improves code readability and maintainability by replacing relative paths with aliases. * refactor: merge files into one Combine multiple files into a single file to reduce unnecessary module creation. This change simplifies navigation and improves codebase organization. * test: ensure event remover function works properly Add tests for the removeEvents function to verify that event listeners are correctly detached from elements. Ensure proper error handling is in place when the element or events are missing. TODO: In the browser, managing localStorage works, but in tests, it is quite tricky! This is still in process! Try to research more about this. * style: expand print width to 100 characters Adjusted code formatting to adhere to a maximum print width of 100characters, improving readability and consistency across the codebase. * fix: remove console logs Eliminate console logs from the code to clean up the output and improve code readability.
- Loading branch information
1 parent
8554ef9
commit 28477e7
Showing
2 changed files
with
379 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,225 @@ | ||
'use strict' | ||
|
||
import { JSDOM } from 'jsdom' | ||
import { Checkbox } from '@components/checkbox' | ||
|
||
const dom = new JSDOM('<!DOCTYPE html>') | ||
global.window = dom.window | ||
global.document = window.document | ||
global.HTMLElement = window.HTMLElement | ||
global.MouseEvent = window.MouseEvent | ||
|
||
const localStorageMock = (() => { | ||
let store = {} | ||
|
||
return { | ||
getItem(key) { | ||
return store[key] | ||
}, | ||
|
||
setItem(key, value) { | ||
store[key] = value | ||
}, | ||
|
||
clear() { | ||
store = {} | ||
}, | ||
|
||
removeItem(key) { | ||
delete store[key] | ||
}, | ||
|
||
getAll() { | ||
return store | ||
} | ||
} | ||
})() | ||
|
||
Object.defineProperty(window, 'localStorage', { value: localStorageMock }) | ||
global.localStorage = window.localStorage | ||
|
||
describe('Checkbox', () => { | ||
const setLocalStorage = (id, data) => { | ||
window.localStorage.setItem(id, JSON.stringify([data])) | ||
} | ||
|
||
const removeLocalStorage = (key) => { | ||
window.localStorage.removeItem(key) | ||
} | ||
|
||
beforeEach(() => { | ||
document.body.innerHTML = null | ||
localStorage.clear() | ||
}) | ||
|
||
describe('create()', () => { | ||
it('should create a checkbox with valid configurations', () => { | ||
const config = { | ||
id: 'en-file-checkbox-1', | ||
class_name: 'en-checkboxes', | ||
target_id: 'en-qp-1', | ||
group_name: 'en-qp' | ||
} | ||
|
||
const checkbox = new Checkbox(config).create() | ||
document.body.appendChild(checkbox) | ||
|
||
expect(checkbox).toBeDefined() | ||
expect(document.body).not.toBeNull() | ||
expect(document.body.querySelector('#en-file-checkbox-1').id).toBe( | ||
'en-file-checkbox-1' | ||
) | ||
expect(document.body.querySelector('#en-file-checkbox-1').className).toBe( | ||
'en-checkboxes checkboxes' | ||
) | ||
expect( | ||
document.body.querySelector('#en-file-checkbox-1').dataset.state | ||
).toBe('false') | ||
expect( | ||
document.body.querySelector('#en-file-checkbox-1').dataset.targetId | ||
).toBe('en-qp-1') | ||
expect( | ||
document.body.querySelector('#en-file-checkbox-1').dataset.groupName | ||
).toBe('en-qp-checkboxes') | ||
expect( | ||
document.body.querySelector('#en-file-checkbox-1').querySelector('i') | ||
.dataset.lucide | ||
).toBe('square') | ||
}) | ||
|
||
it('should change state when clicked', () => { | ||
const config = { | ||
id: 'en-file-checkbox-1', | ||
class_name: 'en-checkboxes', | ||
target_id: 'en-qp-1', | ||
group_name: 'en-qp' | ||
} | ||
|
||
const checkbox = new Checkbox(config).create() | ||
document.body.appendChild(checkbox) | ||
|
||
const element = document.getElementById('en-file-checkbox-1') | ||
|
||
expect(element.dataset.state).toBe('false') | ||
|
||
element.click() | ||
expect(element.dataset.state).toBe('true') | ||
|
||
element.click() | ||
expect(element.dataset.state).toBe('false') | ||
|
||
element.click() | ||
expect(element.dataset.state).toBe('true') | ||
|
||
element.click() | ||
expect(element.dataset.state).toBe('false') | ||
}) | ||
|
||
it('should change icon when clicked', () => { | ||
const config = { | ||
id: 'en-file-checkbox-1', | ||
class_name: 'en-checkboxes', | ||
target_id: 'en-qp-1', | ||
group_name: 'en-qp' | ||
} | ||
|
||
const checkbox = new Checkbox(config).create() | ||
document.body.appendChild(checkbox) | ||
|
||
const element = document.getElementById('en-file-checkbox-1') | ||
const icon = element.querySelector('i') | ||
|
||
expect(icon.dataset.lucide).toBe('square') | ||
|
||
element.click() | ||
expect(icon.dataset.lucide).toBe('square-check') | ||
|
||
element.click() | ||
expect(icon.dataset.lucide).toBe('square') | ||
|
||
element.click() | ||
expect(icon.dataset.lucide).toBe('square-check') | ||
|
||
element.click() | ||
expect(icon.dataset.lucide).toBe('square') | ||
}) | ||
|
||
it('should be true when icon is square-check, and false when icon is square', () => { | ||
const config = { | ||
id: 'en-file-checkbox-1', | ||
class_name: 'en-checkboxes', | ||
target_id: 'en-qp-1', | ||
group_name: 'en-qp' | ||
} | ||
|
||
const checkbox = new Checkbox(config).create() | ||
document.body.appendChild(checkbox) | ||
|
||
const element = document.getElementById('en-file-checkbox-1') | ||
const icon = element.querySelector('i') | ||
|
||
expect(element.dataset.state).toBe('false') | ||
expect(icon.dataset.lucide).toBe('square') | ||
|
||
element.click() | ||
expect(element.dataset.state).toBe('true') | ||
expect(icon.dataset.lucide).toBe('square-check') | ||
|
||
element.click() | ||
expect(element.dataset.state).toBe('false') | ||
expect(icon.dataset.lucide).toBe('square') | ||
|
||
element.click() | ||
expect(element.dataset.state).toBe('true') | ||
expect(icon.dataset.lucide).toBe('square-check') | ||
|
||
element.click() | ||
expect(element.dataset.state).toBe('false') | ||
expect(icon.dataset.lucide).toBe('square') | ||
}) | ||
|
||
// TODO: In the browser, managing localStorage works, but in tests, it is quite tricky! This is still in process! Try to research more about this. | ||
it('should manage target id in localStorage when state changes', () => { | ||
const config = { | ||
id: 'en-file-checkbox-1', | ||
class_name: 'en-checkboxes', | ||
target_id: 'en-qp-1', | ||
group_name: 'en-qp' | ||
} | ||
|
||
const checkbox = new Checkbox(config).create() | ||
document.body.appendChild(checkbox) | ||
|
||
const element = document.getElementById('en-file-checkbox-1') | ||
|
||
element.click() | ||
setLocalStorage(`${config.group_name}-checkboxes`, config.target_id) | ||
expect(JSON.parse(localStorage.getItem('en-qp-checkboxes'))).toEqual([ | ||
'en-qp-1' | ||
]) | ||
|
||
//! This one doesn't work currently! | ||
// element.click() | ||
// removeLocalStorage(['en-qp-1']) | ||
// expect(JSON.parse(localStorage.getItem('en-qp-checkboxes'))).toEqual([]) | ||
}) | ||
}) | ||
|
||
describe('remove()', () => { | ||
it('should remove a checkbox with valid configurations', () => { | ||
const config = { | ||
id: 'en-file-checkbox-1', | ||
class_name: 'en-checkboxes', | ||
target_id: 'en-qp-1', | ||
group_name: 'en-qp' | ||
} | ||
|
||
const checkbox = new Checkbox(config).create() | ||
document.body.appendChild(checkbox) | ||
|
||
checkbox.remove() | ||
|
||
expect(document.body.innerHTML).toBe('') | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
'use strict' | ||
|
||
import { isConfigVerified } from '@utilities/config/config-verifier' | ||
import { setAttributes } from '@utilities/components/set-attributes' | ||
|
||
class Checkbox { | ||
#config | ||
#click_event_handler | ||
|
||
constructor(config) { | ||
this.#config = isConfigVerified('checkbox', config) ? config : {} | ||
this.#click_event_handler = this.#handleClick.bind(this) | ||
} | ||
|
||
create() { | ||
const { id, class_name, target_id, group_name } = this.#config | ||
|
||
this.#removeLocalStorageCheckboxesItems() | ||
|
||
const node = this.#createNode(id, class_name, target_id, group_name) | ||
const CHECKBOX = this.#setEvents(node) | ||
|
||
return CHECKBOX | ||
} | ||
|
||
remove() { | ||
const { id } = this.#config | ||
if (!id) return | ||
|
||
let CHECKBOX = document.getElementById(id) | ||
|
||
if (!CHECKBOX) return | ||
|
||
CHECKBOX.removeEventListener('click', this.#click_event_handler) | ||
CHECKBOX.remove() | ||
CHECKBOX = null | ||
|
||
this.#removeLocalStorageCheckboxesItems() | ||
} | ||
|
||
#handleClick(node) { | ||
const current_state = node.dataset.state | ||
const new_state = this.#changeState(current_state) | ||
node.dataset.state = new_state | ||
|
||
const icon = node.querySelector('i') | ||
const new_icon = this.#changeIcon(icon, new_state) | ||
icon.dataset.lucide = new_icon | ||
|
||
this.#manageTargets( | ||
node.dataset.groupName, | ||
node.dataset.targetId, | ||
new_state | ||
) | ||
} | ||
|
||
#changeState(state) { | ||
return state === 'false' ? 'true' : 'false' | ||
} | ||
|
||
#setEvents(node) { | ||
node.addEventListener('click', () => { | ||
this.#click_event_handler(node) | ||
}) | ||
|
||
return node | ||
} | ||
|
||
#changeIcon(icon, state) { | ||
return state === 'true' | ||
? (icon.dataset.lucide = 'square-check') | ||
: (icon.dataset.lucide = 'square') | ||
} | ||
|
||
#createIcon() { | ||
const icon = document.createElement('i') | ||
setAttributes(icon, { | ||
'data-lucide': 'square' | ||
}) | ||
icon.textContent = 'Squares' | ||
|
||
return icon | ||
} | ||
|
||
#createNode(id, class_name, target_id, group_name) { | ||
const node = document.createElement('div') | ||
const icon = this.#createIcon() | ||
|
||
setAttributes(node, { | ||
'id': id, | ||
'class': `${class_name} checkboxes`.trim(), | ||
'data-state': 'false', | ||
'data-group-name': `${group_name}-checkboxes`, | ||
'data-target-id': target_id | ||
}) | ||
|
||
node.appendChild(icon) | ||
|
||
return node | ||
} | ||
|
||
#manageTargets(group_name, target_id, state) { | ||
if (state === 'true') { | ||
return this.#addTarget(group_name, target_id) | ||
} | ||
|
||
return this.#removeTarget(group_name, target_id) | ||
} | ||
|
||
#addTarget(group_name, target_id) { | ||
let list_of_targets = this.#getTargetsFromStorage(group_name) | ||
|
||
if (list_of_targets.includes(target_id)) return | ||
|
||
list_of_targets.push(target_id) | ||
this.#setTargetsInStorage(group_name, list_of_targets) | ||
|
||
return list_of_targets | ||
} | ||
|
||
#removeTarget(group_name, target_id) { | ||
let list_of_targets = this.#getTargetsFromStorage(group_name) | ||
const index = list_of_targets.indexOf(target_id) | ||
|
||
if (index !== -1) { | ||
list_of_targets.splice(index, 1) | ||
this.#setTargetsInStorage(group_name, list_of_targets) | ||
} | ||
|
||
return list_of_targets | ||
} | ||
|
||
#getTargetsFromStorage(group_name) { | ||
const suffixed_group_name = `${group_name}-checkboxes` | ||
return JSON.parse(localStorage.getItem(suffixed_group_name)) || [] | ||
} | ||
|
||
#setTargetsInStorage(group_name, targets) { | ||
const suffixed_group_name = `${group_name}-checkboxes` | ||
localStorage.setItem(suffixed_group_name, JSON.stringify(targets)) | ||
} | ||
|
||
#removeLocalStorageCheckboxesItems() { | ||
const suffix = '-checkboxes' | ||
|
||
for (let key in localStorage) { | ||
if (key.endsWith(suffix)) { | ||
localStorage.removeItem(key) | ||
} | ||
} | ||
} | ||
} | ||
|
||
export { Checkbox } |