This commit fixes the bug where the "Copy" button does not copy when
clicked on download instructions modal (on Linux and macOS).
This commit also introduces several improvements to the UI components
related to copy action and their interaction with the clipboard feature.
It adds more tests to avoid regression of the bugs and improves
maintainability, testability and adherence to Vue's reactive principles.
Changes include:
- Fix non-responsive copy button in the download instructions modal by
triggering a `click` event in `AppIcon.vue`.
- Improve `TheCodeButtons.vue`:
- Remove redundant `getCurrentCode` function.
- Separate components for each button for better separation of
concerns and higher testability.
- Use the `gap` property in the flexbox layout, replacing the less
explicit sibling combinator approach.
- Add `useClipboard` compositional hook for more idiomatic Vue approach
to interacting with the clipboard.
- Add `useCurrentCode` compositional hook to handle current code state
more effectively with unified logic.
- Abstract clipboard operations to an interface to isolate
responsibilities.
- Switch clipboard implementation to the `navigator.clipboard` API,
moving away from the deprecated `document.execCommand`.
- Move clipboard logic to the presentation layer to conform to
separation of concerns and domain-driven design principles.
- Improve `IconButton.vue` component to increase reusability with
consistent sizing.
129 lines
4.7 KiB
TypeScript
129 lines
4.7 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { IEventSubscriptionCollection } from '@/infrastructure/Events/IEventSubscriptionCollection';
|
|
import { useCurrentCode } from '@/presentation/components/Shared/Hooks/UseCurrentCode';
|
|
import { ApplicationCodeStub } from '@tests/unit/shared/Stubs/ApplicationCodeStub';
|
|
import { CategoryCollectionStateStub } from '@tests/unit/shared/Stubs/CategoryCollectionStateStub';
|
|
import { EventSubscriptionCollectionStub } from '@tests/unit/shared/Stubs/EventSubscriptionCollectionStub';
|
|
import { UseCollectionStateStub } from '@tests/unit/shared/Stubs/UseCollectionStateStub';
|
|
import { CodeChangedEventStub } from '@tests/unit/shared/Stubs/CodeChangedEventStub';
|
|
|
|
describe('useCurrentCode', () => {
|
|
describe('currentCode', () => {
|
|
it('gets code from initial state', () => {
|
|
// arrange
|
|
const expectedCode = 'initial code';
|
|
const { codeStub, collectionStateStub } = createStubs();
|
|
codeStub.withCurrentCode(expectedCode);
|
|
const useCollectionStateStub = new UseCollectionStateStub();
|
|
const { currentCode } = new UseCurrentCodeBuilder()
|
|
.withUseCollectionState(useCollectionStateStub)
|
|
.build();
|
|
// act
|
|
useCollectionStateStub.triggerOnStateChange({
|
|
newState: collectionStateStub,
|
|
immediateOnly: true, // set initial state
|
|
});
|
|
// assert
|
|
const actualCode = currentCode.value;
|
|
expect(actualCode).to.equal(expectedCode);
|
|
});
|
|
it('updates code state code is changed', () => {
|
|
// arrange
|
|
const initialCode = 'initial code';
|
|
const expectedCode = 'changed code';
|
|
const {
|
|
codeStub: initialCodeStub,
|
|
collectionStateStub: initialCollectionStateStub,
|
|
} = createStubs();
|
|
initialCodeStub.withCurrentCode(initialCode);
|
|
const useCollectionStateStub = new UseCollectionStateStub();
|
|
const { currentCode } = new UseCurrentCodeBuilder()
|
|
.withUseCollectionState(useCollectionStateStub)
|
|
.build();
|
|
useCollectionStateStub.triggerOnStateChange({
|
|
newState: initialCollectionStateStub,
|
|
immediateOnly: true, // set initial state
|
|
});
|
|
const {
|
|
codeStub: changedCodeStub,
|
|
collectionStateStub: changedStateStub,
|
|
} = createStubs();
|
|
changedCodeStub.withCurrentCode(expectedCode);
|
|
// act
|
|
useCollectionStateStub.triggerOnStateChange({
|
|
newState: changedStateStub,
|
|
immediateOnly: true, // update state
|
|
});
|
|
// assert
|
|
const actualCode = currentCode.value;
|
|
expect(actualCode).to.equal(expectedCode);
|
|
});
|
|
it('updates code when code is changed', () => {
|
|
// arrange
|
|
const expectedCode = 'changed code';
|
|
const { codeStub, collectionStateStub } = createStubs();
|
|
const { currentCode } = new UseCurrentCodeBuilder()
|
|
.withCollectionState(collectionStateStub)
|
|
.build();
|
|
// act
|
|
codeStub.triggerCodeChange(new CodeChangedEventStub().withCode(expectedCode));
|
|
// assert
|
|
const actualCode = currentCode.value;
|
|
expect(expectedCode).to.equal(actualCode);
|
|
});
|
|
it('registers event subscription on creation', () => {
|
|
// arrange
|
|
const eventsStub = new EventSubscriptionCollectionStub();
|
|
const stateStub = new UseCollectionStateStub();
|
|
// act
|
|
new UseCurrentCodeBuilder()
|
|
.withUseCollectionState(stateStub)
|
|
.withEvents(eventsStub)
|
|
.build();
|
|
// assert
|
|
const calls = eventsStub.callHistory;
|
|
expect(calls).has.lengthOf(1);
|
|
const call = calls.find((c) => c.methodName === 'unsubscribeAllAndRegister');
|
|
expect(call).toBeDefined();
|
|
});
|
|
});
|
|
});
|
|
|
|
function createStubs() {
|
|
const codeStub = new ApplicationCodeStub();
|
|
const collectionStateStub = new CategoryCollectionStateStub().withCode(codeStub);
|
|
const useStateStub = new UseCollectionStateStub()
|
|
.withState(collectionStateStub);
|
|
return {
|
|
codeStub,
|
|
useStateStub,
|
|
collectionStateStub,
|
|
};
|
|
}
|
|
|
|
class UseCurrentCodeBuilder {
|
|
private useCollectionState: UseCollectionStateStub = new UseCollectionStateStub();
|
|
|
|
private events: IEventSubscriptionCollection = new EventSubscriptionCollectionStub();
|
|
|
|
public withUseCollectionState(useCollectionState: UseCollectionStateStub): this {
|
|
this.useCollectionState = useCollectionState;
|
|
return this;
|
|
}
|
|
|
|
public withCollectionState(collectionState: CategoryCollectionStateStub): this {
|
|
return this.withUseCollectionState(
|
|
this.useCollectionState.withState(collectionState),
|
|
);
|
|
}
|
|
|
|
public withEvents(events: IEventSubscriptionCollection): this {
|
|
this.events = events;
|
|
return this;
|
|
}
|
|
|
|
public build(): ReturnType<typeof useCurrentCode> {
|
|
return useCurrentCode(this.useCollectionState.get(), this.events);
|
|
}
|
|
}
|