Improve selection type documentation
- Refine tooltip documentation with clearer information. - Introduce privacy ranking indicator for intuitive user guidance. - Adopt a consistent format throughout documentation. - Switch from emojis to icons to maintain visual uniformity.
This commit is contained in:
1
src/presentation/assets/icons/lightbulb.svg
Normal file
1
src/presentation/assets/icons/lightbulb.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 384 512"><path d="M272 384c9.6-31.9 29.5-59.1 49.2-86.2l0 0c5.2-7.1 10.4-14.2 15.4-21.4c19.8-28.5 31.4-63 31.4-100.3C368 78.8 289.2 0 192 0S16 78.8 16 176c0 37.3 11.6 71.9 31.4 100.3c5 7.2 10.2 14.3 15.4 21.4l0 0c19.8 27.1 39.7 54.4 49.2 86.2H272zM192 512c44.2 0 80-35.8 80-80V416H112v16c0 44.2 35.8 80 80 80zM112 176c0 8.8-7.2 16-16 16s-16-7.2-16-16c0-61.9 50.1-112 112-112c8.8 0 16 7.2 16 16s-7.2 16-16 16c-44.2 0-80 35.8-80 80z"/></svg>
|
||||||
|
After Width: | Height: | Size: 536 B |
1
src/presentation/assets/icons/square-check.svg
Normal file
1
src/presentation/assets/icons/square-check.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 448 512"><path d="M64 32C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64H384c35.3 0 64-28.7 64-64V96c0-35.3-28.7-64-64-64H64zM337 209L209 337c-9.4 9.4-24.6 9.4-33.9 0l-64-64c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l47 47L303 175c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9z"/></svg>
|
||||||
|
After Width: | Height: | Size: 372 B |
1
src/presentation/assets/icons/triangle-exclamation.svg
Normal file
1
src/presentation/assets/icons/triangle-exclamation.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 512 512"><path d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480H40c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24V296c0 13.3 10.7 24 24 24s24-10.7 24-24V184c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"/></svg>
|
||||||
|
After Width: | Height: | Size: 427 B |
@@ -30,6 +30,10 @@ $color-on-surface : #4d5156;
|
|||||||
// Background | Appears behind scrollable content.
|
// Background | Appears behind scrollable content.
|
||||||
$color-background : #e6ecf4;
|
$color-background : #e6ecf4;
|
||||||
|
|
||||||
|
$color-success : #4CAF50;
|
||||||
|
$color-danger : #F44336;
|
||||||
|
$color-caution : #FFC107;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Application-specific colors:
|
Application-specific colors:
|
||||||
These are tailored to the specific needs of the application and derived from the above theme colors.
|
These are tailored to the specific needs of the application and derived from the above theme colors.
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<template>
|
||||||
|
<span class="circle-rating">
|
||||||
|
<RatingCircle
|
||||||
|
v-for="i in maxRating"
|
||||||
|
:key="i"
|
||||||
|
:filled="i <= rating"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import RatingCircle from './RatingCircle.vue';
|
||||||
|
|
||||||
|
const minRating = 0;
|
||||||
|
const maxRating = 4;
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: {
|
||||||
|
RatingCircle,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
rating: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
validator: (value: number) => {
|
||||||
|
return value >= minRating && value <= maxRating;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup() {
|
||||||
|
return {
|
||||||
|
maxRating,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.circle-rating {
|
||||||
|
display: inline-flex;
|
||||||
|
gap: 0.2em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
<template>
|
||||||
|
<svg
|
||||||
|
:style="{
|
||||||
|
'--circle-stroke-width': `${circleStrokeWidthInPx}px`,
|
||||||
|
}"
|
||||||
|
:viewBox="viewBox"
|
||||||
|
>
|
||||||
|
<circle
|
||||||
|
:cx="circleRadiusInPx"
|
||||||
|
:cy="circleRadiusInPx"
|
||||||
|
:r="circleRadiusWithoutStrokeInPx"
|
||||||
|
:class="{
|
||||||
|
filled,
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, computed } from 'vue';
|
||||||
|
|
||||||
|
const circleDiameterInPx = 20;
|
||||||
|
const circleStrokeWidthInPx = 2;
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
|
filled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup() {
|
||||||
|
const circleRadiusInPx = computed(() => {
|
||||||
|
return circleDiameterInPx / 2;
|
||||||
|
});
|
||||||
|
const circleRadiusWithoutStrokeInPx = computed(() => {
|
||||||
|
return circleRadiusInPx.value - (circleStrokeWidthInPx / 2);
|
||||||
|
});
|
||||||
|
const viewBox = computed(() => {
|
||||||
|
const minX = -circleStrokeWidthInPx / 2;
|
||||||
|
const minY = -circleStrokeWidthInPx / 2;
|
||||||
|
const width = circleDiameterInPx + circleStrokeWidthInPx;
|
||||||
|
const height = circleDiameterInPx + circleStrokeWidthInPx;
|
||||||
|
return `${minX} ${minY} ${width} ${height}`;
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
circleRadiusInPx,
|
||||||
|
circleDiameterInPx,
|
||||||
|
circleStrokeWidthInPx,
|
||||||
|
circleRadiusWithoutStrokeInPx,
|
||||||
|
viewBox,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
$circleColor: currentColor;
|
||||||
|
$circleHeight: 0.8em;
|
||||||
|
$circleStrokeWidth: var(--circle-stroke-width);
|
||||||
|
|
||||||
|
svg {
|
||||||
|
height: $circleHeight;
|
||||||
|
circle {
|
||||||
|
stroke: $circleColor;
|
||||||
|
stroke-width: $circleStrokeWidth;
|
||||||
|
&.filled {
|
||||||
|
fill: $circleColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<p class="privacy-rating">
|
||||||
|
Privacy: <CircleRating :rating="privacyRating" />
|
||||||
|
</p>
|
||||||
|
<hr />
|
||||||
|
<div class="sections">
|
||||||
|
<section>
|
||||||
|
{{ description }}
|
||||||
|
</section>
|
||||||
|
<section class="recommendation">
|
||||||
|
<AppIcon icon="lightbulb" class="icon" />
|
||||||
|
<span class="text">{{ recommendation }}</span>
|
||||||
|
</section>
|
||||||
|
<section
|
||||||
|
v-if="includes?.length > 0"
|
||||||
|
class="includes"
|
||||||
|
>
|
||||||
|
<AppIcon icon="square-check" class="icon" />
|
||||||
|
<span class="text">
|
||||||
|
Includes:
|
||||||
|
<ul>
|
||||||
|
<li
|
||||||
|
v-for="inclusionItem in includes"
|
||||||
|
:key="inclusionItem"
|
||||||
|
>
|
||||||
|
{{ inclusionItem }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</span>
|
||||||
|
</section>
|
||||||
|
<section
|
||||||
|
v-if="considerations?.length > 0"
|
||||||
|
class="considerations"
|
||||||
|
>
|
||||||
|
<AppIcon icon="triangle-exclamation" class="icon" />
|
||||||
|
<span class="text">
|
||||||
|
Considerations:
|
||||||
|
<ul>
|
||||||
|
<li
|
||||||
|
v-for="considerationItem in considerations"
|
||||||
|
:key="considerationItem"
|
||||||
|
>
|
||||||
|
{{ considerationItem }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</span>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { PropType, defineComponent } from 'vue';
|
||||||
|
import AppIcon from '@/presentation/components/Shared/Icon/AppIcon.vue';
|
||||||
|
import CircleRating from './Rating/CircleRating.vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: {
|
||||||
|
CircleRating,
|
||||||
|
AppIcon,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
privacyRating: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
recommendation: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
includes: {
|
||||||
|
type: Array as PropType<ReadonlyArray<string>>,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
considerations: {
|
||||||
|
type: Array as PropType<ReadonlyArray<string>>,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@use "@/presentation/assets/styles/main" as *;
|
||||||
|
|
||||||
|
.privacy-rating {
|
||||||
|
margin: 0.5em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
hr {
|
||||||
|
margin: 1em 0;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
ul {
|
||||||
|
@include reset-ul;
|
||||||
|
padding-left: 0em;
|
||||||
|
margin-top: 0.25em;
|
||||||
|
list-style: disc;
|
||||||
|
li {
|
||||||
|
line-height: 1.2em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sections {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.75em;
|
||||||
|
margin-bottom: 0.75em;
|
||||||
|
.includes {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5em;
|
||||||
|
font-weight: 500;
|
||||||
|
.icon {
|
||||||
|
color: $color-success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.considerations {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5em;
|
||||||
|
.text {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.icon {
|
||||||
|
color: $color-danger;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.recommendation {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5em;
|
||||||
|
.icon {
|
||||||
|
color: $color-caution;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -8,9 +8,11 @@
|
|||||||
@click="selectType(SelectionType.None)"
|
@click="selectType(SelectionType.None)"
|
||||||
/>
|
/>
|
||||||
<template #tooltip>
|
<template #tooltip>
|
||||||
Deselect all selected scripts.
|
<SelectionTypeDocumentation
|
||||||
<br />
|
:privacy-rating="0"
|
||||||
💡 Good start to dive deeper into tweaks and select only what you want.
|
description="Deselects all scripts. Good starting point to review and select individual tweaks."
|
||||||
|
recommendation="Recommended for users who prefer total control over changes. It allows you to examine and select only the tweaks you require."
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</TooltipWrapper>
|
</TooltipWrapper>
|
||||||
|
|
||||||
@@ -22,11 +24,16 @@
|
|||||||
@click="selectType(SelectionType.Standard)"
|
@click="selectType(SelectionType.Standard)"
|
||||||
/>
|
/>
|
||||||
<template #tooltip>
|
<template #tooltip>
|
||||||
🛡️ Balanced for privacy and functionality.
|
<SelectionTypeDocumentation
|
||||||
<br />
|
:privacy-rating="2"
|
||||||
OS and applications will function normally.
|
description="Provides a balanced approach between privacy and functionality."
|
||||||
<br />
|
recommendation="Recommended for most users who wish to improve privacy with best-practices without affecting stability."
|
||||||
💡 Recommended for everyone
|
:includes="[
|
||||||
|
'Retains functionality of all apps and system services.',
|
||||||
|
'Clears non-essential OS and app telemetry data and caches.',
|
||||||
|
'Keeps essential security services enabled.',
|
||||||
|
]"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</TooltipWrapper>
|
</TooltipWrapper>
|
||||||
|
|
||||||
@@ -38,11 +45,20 @@
|
|||||||
@click="selectType(SelectionType.Strict)"
|
@click="selectType(SelectionType.Strict)"
|
||||||
/>
|
/>
|
||||||
<template #tooltip>
|
<template #tooltip>
|
||||||
🚫 Stronger privacy, disables risky functions that may leak your data.
|
<SelectionTypeDocumentation
|
||||||
<br />
|
:privacy-rating="3"
|
||||||
⚠️ Double check to remove scripts where you would trade functionality for privacy
|
description="Focuses heavily on privacy by disabling some non-critical functions that could leak data."
|
||||||
<br />
|
recommendation="Recommended for advanced users who prioritize privacy over non-essential functionality."
|
||||||
💡 Recommended for daily users that prefers more privacy over non-essential functions
|
:includes="[
|
||||||
|
'Disables optional OS and app services that could leak data.',
|
||||||
|
'Clears non-essential caches, histories, temporary files while retaining browser bookmarks.',
|
||||||
|
'Keeps vital security services and critical application functionality.',
|
||||||
|
]"
|
||||||
|
:considerations="[
|
||||||
|
'Review each script to make sure you are comfortable with the disabled functionality.',
|
||||||
|
'Some non-critical applications or features may no longer function as expected.',
|
||||||
|
]"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</TooltipWrapper>
|
</TooltipWrapper>
|
||||||
|
|
||||||
@@ -54,11 +70,15 @@
|
|||||||
@click="selectType(SelectionType.All)"
|
@click="selectType(SelectionType.All)"
|
||||||
/>
|
/>
|
||||||
<template #tooltip>
|
<template #tooltip>
|
||||||
🔒 Strongest privacy, disabling any functionality that may leak your data.
|
<SelectionTypeDocumentation
|
||||||
<br />
|
:privacy-rating="4"
|
||||||
🛑 Not designed for daily users, it will break important functionalities.
|
description="Strongest privacy by disabling any functionality that may risk data exposure."
|
||||||
<br />
|
recommendation="Recommended for extreme use cases where no data leak is acceptable like crime labs."
|
||||||
💡 Only recommended for extreme use-cases like crime labs where no leak is acceptable
|
:considerations="[
|
||||||
|
'Not recommended for daily use as it breaks important functionality.',
|
||||||
|
'Do not run it without having backups and system snapshots, unless you\'re on a disposable system.',
|
||||||
|
]"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</TooltipWrapper>
|
</TooltipWrapper>
|
||||||
</MenuOptionList>
|
</MenuOptionList>
|
||||||
@@ -74,12 +94,14 @@ import { ICategoryCollection } from '@/domain/ICategoryCollection';
|
|||||||
import MenuOptionList from '../MenuOptionList.vue';
|
import MenuOptionList from '../MenuOptionList.vue';
|
||||||
import MenuOptionListItem from '../MenuOptionListItem.vue';
|
import MenuOptionListItem from '../MenuOptionListItem.vue';
|
||||||
import { SelectionType, setCurrentSelectionType, getCurrentSelectionType } from './SelectionTypeHandler';
|
import { SelectionType, setCurrentSelectionType, getCurrentSelectionType } from './SelectionTypeHandler';
|
||||||
|
import SelectionTypeDocumentation from './SelectionTypeDocumentation.vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
MenuOptionList,
|
MenuOptionList,
|
||||||
MenuOptionListItem,
|
MenuOptionListItem,
|
||||||
TooltipWrapper,
|
TooltipWrapper,
|
||||||
|
SelectionTypeDocumentation,
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const {
|
const {
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ export const IconNames = [
|
|||||||
'file-arrow-down',
|
'file-arrow-down',
|
||||||
'floppy-disk',
|
'floppy-disk',
|
||||||
'play',
|
'play',
|
||||||
|
'lightbulb',
|
||||||
|
'square-check',
|
||||||
|
'triangle-exclamation',
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export type IconName = typeof IconNames[number];
|
export type IconName = typeof IconNames[number];
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { shallowMount } from '@vue/test-utils';
|
||||||
|
import CircleRating from '@/presentation/components/Scripts/Menu/Selector/Rating/CircleRating.vue';
|
||||||
|
import RatingCircle from '@/presentation/components/Scripts/Menu/Selector/Rating/RatingCircle.vue';
|
||||||
|
|
||||||
|
const MAX_RATING = 4;
|
||||||
|
|
||||||
|
describe('CircleRating.vue', () => {
|
||||||
|
describe('number of RatingCircle components', () => {
|
||||||
|
it('renders correct number of RatingCircle components based on maxRating', () => {
|
||||||
|
// arrange
|
||||||
|
const expectedMaxRating = MAX_RATING;
|
||||||
|
const currentRating = MAX_RATING - 1;
|
||||||
|
|
||||||
|
// act
|
||||||
|
const wrapper = shallowMount(CircleRating, {
|
||||||
|
propsData: {
|
||||||
|
rating: currentRating,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// assert
|
||||||
|
const ratingCircles = wrapper.findAllComponents(RatingCircle);
|
||||||
|
expect(ratingCircles.length).to.equal(expectedMaxRating);
|
||||||
|
});
|
||||||
|
it('renders the correct number of RatingCircle components for default rating', () => {
|
||||||
|
// arrange
|
||||||
|
const expectedMaxRating = MAX_RATING;
|
||||||
|
|
||||||
|
// act
|
||||||
|
const wrapper = shallowMount(CircleRating);
|
||||||
|
|
||||||
|
// assert
|
||||||
|
const ratingCircles = wrapper.findAllComponents(RatingCircle);
|
||||||
|
expect(ratingCircles.length).to.equal(expectedMaxRating);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('rating logic', () => {
|
||||||
|
it('fills the correct number of RatingCircle components based on the provided rating', () => {
|
||||||
|
// arrange
|
||||||
|
const expectedTotalComponents = 3;
|
||||||
|
|
||||||
|
// act
|
||||||
|
const wrapper = shallowMount(CircleRating, {
|
||||||
|
propsData: {
|
||||||
|
rating: expectedTotalComponents,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// assert
|
||||||
|
const filledCircles = wrapper.findAllComponents(RatingCircle).filter((w) => w.props().filled);
|
||||||
|
expect(filledCircles.length).to.equal(expectedTotalComponents);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('validates rating correctly', () => {
|
||||||
|
const testCases = [
|
||||||
|
{
|
||||||
|
value: -1,
|
||||||
|
expectedValidationResult: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 0,
|
||||||
|
expectedValidationResult: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: MAX_RATING - 1,
|
||||||
|
expectedValidationResult: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: MAX_RATING,
|
||||||
|
expectedValidationResult: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
testCases.forEach((testCase) => {
|
||||||
|
it(`given ${testCase.value} return ${testCase.expectedValidationResult ? 'true' : 'false'}`, () => {
|
||||||
|
// arrange
|
||||||
|
const { validator } = CircleRating.props.rating;
|
||||||
|
|
||||||
|
// act
|
||||||
|
const actualValidationResult = validator(testCase.value);
|
||||||
|
|
||||||
|
// act
|
||||||
|
expect(actualValidationResult).to.equal(testCase.expectedValidationResult);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { shallowMount } from '@vue/test-utils';
|
||||||
|
import RatingCircle from '@/presentation/components/Scripts/Menu/Selector/Rating/RatingCircle.vue';
|
||||||
|
|
||||||
|
const DOM_SVG_SELECTOR = 'svg';
|
||||||
|
const DOM_CIRCLE_SELECTOR = `${DOM_SVG_SELECTOR} > circle`;
|
||||||
|
const DOM_CIRCLE_FILLED_SELECTOR = `${DOM_CIRCLE_SELECTOR}.filled`;
|
||||||
|
|
||||||
|
describe('RatingCircle.vue', () => {
|
||||||
|
describe('circle appearance', () => {
|
||||||
|
it('renders a circle with the correct styles when filled', () => {
|
||||||
|
const wrapper = shallowMount(RatingCircle, {
|
||||||
|
propsData: {
|
||||||
|
filled: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const circle = wrapper.find(DOM_CIRCLE_FILLED_SELECTOR);
|
||||||
|
expect(circle.exists()).to.equal(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders a circle without filled styles when not filled', () => {
|
||||||
|
const wrapper = shallowMount(RatingCircle, {
|
||||||
|
propsData: {
|
||||||
|
filled: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const circle = wrapper.find(DOM_CIRCLE_FILLED_SELECTOR);
|
||||||
|
expect(circle.exists()).to.equal(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders without filled styles when filled prop is not provided', () => {
|
||||||
|
const wrapper = shallowMount(RatingCircle);
|
||||||
|
|
||||||
|
const circle = wrapper.find(DOM_CIRCLE_FILLED_SELECTOR);
|
||||||
|
expect(circle.exists()).to.equal(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('SVG and circle styles', () => {
|
||||||
|
it('sets --circle-stroke-width style correctly', () => {
|
||||||
|
const wrapper = shallowMount(RatingCircle);
|
||||||
|
const svgElement = wrapper.find(DOM_SVG_SELECTOR).element;
|
||||||
|
expect(svgElement.style.getPropertyValue('--circle-stroke-width')).to.equal('2px');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders circle with correct fill attribute when filled prop is true', () => {
|
||||||
|
const wrapper = shallowMount(RatingCircle, {
|
||||||
|
propsData: {
|
||||||
|
filled: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const circleElement = wrapper.find(DOM_CIRCLE_FILLED_SELECTOR);
|
||||||
|
|
||||||
|
expect(circleElement.classes()).to.include('filled');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders circle with the correct viewBox property', () => {
|
||||||
|
const wrapper = shallowMount(RatingCircle);
|
||||||
|
const circle = wrapper.find(DOM_SVG_SELECTOR);
|
||||||
|
|
||||||
|
expect(circle.attributes('viewBox')).to.equal('-1 -1 22 22');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('circle attributes', () => {
|
||||||
|
it('renders circle with the correct cx attribute', () => {
|
||||||
|
const wrapper = shallowMount(RatingCircle);
|
||||||
|
const circleElement = wrapper.find(DOM_CIRCLE_SELECTOR);
|
||||||
|
|
||||||
|
expect(circleElement.attributes('cx')).to.equal('10'); // Based on circleDiameterInPx = 20
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders circle with the correct cy attribute', () => {
|
||||||
|
const wrapper = shallowMount(RatingCircle);
|
||||||
|
const circleElement = wrapper.find(DOM_CIRCLE_SELECTOR);
|
||||||
|
|
||||||
|
expect(circleElement.attributes('cy')).to.equal('10'); // Based on circleDiameterInPx = 20
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders circle with the correct r attribute', () => {
|
||||||
|
const wrapper = shallowMount(RatingCircle);
|
||||||
|
const circleElement = wrapper.find(DOM_CIRCLE_SELECTOR);
|
||||||
|
|
||||||
|
expect(circleElement.attributes('r')).to.equal('9'); // Based on circleRadiusWithoutStrokeInPx = circleDiameterInPx / 2 - circleStrokeWidthInPx / 2
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import { shallowMount } from '@vue/test-utils';
|
||||||
|
import SelectionTypeDocumentation from '@/presentation/components/Scripts/Menu/Selector/SelectionTypeDocumentation.vue';
|
||||||
|
import CircleRating from '@/presentation/components/Scripts/Menu/Selector/Rating/CircleRating.vue';
|
||||||
|
|
||||||
|
const DOM_SELECTOR_INCLUDES_SECTION = '.includes';
|
||||||
|
const DOM_SELECTOR_CONSIDERATIONS_SECTION = '.considerations';
|
||||||
|
|
||||||
|
describe('SelectionTypeDocumentation.vue', () => {
|
||||||
|
it('renders privacy rating using CircleRating component', () => {
|
||||||
|
// arrange
|
||||||
|
const expectedPrivacyRating = 3;
|
||||||
|
|
||||||
|
// act
|
||||||
|
const wrapper = mountComponent({
|
||||||
|
privacyRating: expectedPrivacyRating,
|
||||||
|
});
|
||||||
|
|
||||||
|
// assert
|
||||||
|
const ratingComponent = wrapper.findComponent(CircleRating);
|
||||||
|
expect(ratingComponent.exists()).to.equal(true);
|
||||||
|
expect(ratingComponent.props().rating).to.equal(expectedPrivacyRating);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the provided description', () => {
|
||||||
|
// arrange
|
||||||
|
const expectedDescription = 'Some description';
|
||||||
|
|
||||||
|
// act
|
||||||
|
const wrapper = mountComponent({
|
||||||
|
description: expectedDescription,
|
||||||
|
});
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(wrapper.text()).to.include(expectedDescription);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the provided recommendation', () => {
|
||||||
|
// arrange
|
||||||
|
const expectedRecommendation = 'Some recommendation';
|
||||||
|
|
||||||
|
// act
|
||||||
|
const wrapper = mountComponent({
|
||||||
|
recommendation: expectedRecommendation,
|
||||||
|
});
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(wrapper.text()).to.include(expectedRecommendation);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('includes', () => {
|
||||||
|
it('renders items if provided', () => {
|
||||||
|
// arrange
|
||||||
|
const expectedIncludes = ['Item 1', 'Item 2'];
|
||||||
|
|
||||||
|
// act
|
||||||
|
const wrapper = mountComponent({
|
||||||
|
includes: expectedIncludes,
|
||||||
|
});
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(wrapper.text()).to.include(expectedIncludes[0]);
|
||||||
|
expect(wrapper.text()).to.include(expectedIncludes[1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders included section if provided', () => {
|
||||||
|
// arrange
|
||||||
|
// act
|
||||||
|
const wrapper = mountComponent({
|
||||||
|
includes: ['some', 'includes'],
|
||||||
|
});
|
||||||
|
|
||||||
|
// assert
|
||||||
|
const includesSection = wrapper.find(DOM_SELECTOR_INCLUDES_SECTION);
|
||||||
|
expect(includesSection.exists()).to.equal(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not render included section if no items provided', () => {
|
||||||
|
// arrange
|
||||||
|
// act
|
||||||
|
const wrapper = mountComponent({
|
||||||
|
includes: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
// assert
|
||||||
|
const includesSection = wrapper.find(DOM_SELECTOR_INCLUDES_SECTION);
|
||||||
|
expect(includesSection.exists()).to.equal(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('considerations', () => {
|
||||||
|
it('renders if provided', () => {
|
||||||
|
// arrange
|
||||||
|
const expectedConsiderations = ['Consideration 1', 'Consideration 2'];
|
||||||
|
|
||||||
|
// act
|
||||||
|
const wrapper = mountComponent({
|
||||||
|
considerations: expectedConsiderations,
|
||||||
|
});
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(wrapper.text()).to.include(expectedConsiderations[0]);
|
||||||
|
expect(wrapper.text()).to.include(expectedConsiderations[1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders included section if provided', () => {
|
||||||
|
// arrange
|
||||||
|
// act
|
||||||
|
const wrapper = mountComponent({
|
||||||
|
considerations: ['some', 'considerations'],
|
||||||
|
});
|
||||||
|
|
||||||
|
// assert
|
||||||
|
const considerationsSection = wrapper.find(DOM_SELECTOR_CONSIDERATIONS_SECTION);
|
||||||
|
expect(considerationsSection.exists()).to.equal(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not render considerations section if no items provided', () => {
|
||||||
|
// arrange
|
||||||
|
// act
|
||||||
|
const wrapper = mountComponent({
|
||||||
|
considerations: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
// assert
|
||||||
|
const considerationsSection = wrapper.find(DOM_SELECTOR_CONSIDERATIONS_SECTION);
|
||||||
|
expect(considerationsSection.exists()).to.equal(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function mountComponent(options: {
|
||||||
|
readonly privacyRating?: number,
|
||||||
|
readonly description?: string,
|
||||||
|
readonly recommendation?: string,
|
||||||
|
readonly includes?: string[],
|
||||||
|
readonly considerations?: string[],
|
||||||
|
}) {
|
||||||
|
return shallowMount(SelectionTypeDocumentation, {
|
||||||
|
propsData: {
|
||||||
|
privacyRating: options.privacyRating ?? 0,
|
||||||
|
description: options.description ?? 'test-description',
|
||||||
|
recommendation: options.recommendation ?? 'test-recommendation',
|
||||||
|
considerations: options.considerations ?? [],
|
||||||
|
includes: options.includes ?? [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user