fix(app): resolve Bucket E.2-E.5 lint findings
WS-3 session 1b-ii Task 5b+c (audit Bucket E.2-E.5 — 6 items resolved,
2 promise/no-promise-in-callback warnings remain on dynamic-import
sites — see deviations).
This commit is split out from the originally-planned grouped Task 5
because the API stream timed out mid-session. E.1 (isAxiosError) is in
the preceding commit 0f155d9.
E.2 — vitest spec to Composition API (1× vue/component-api-style):
- useFormFailures.spec.ts: rewrote the test wrapper from
\`{ setup() { return { result } }, render: () => h('div') }\`
to \`setup(_, { expose }) { expose({ result }); return () => h('div') }\`.
Pure Composition API: setup returns the render function; expose()
declares the instance-visible \`result\` that the 7 \`vm.result.*\`
assertions consume. Tests still pass green (49 tests).
E.3 — REAL BUG: missing return in computed (1× vue/return-in-computed-property):
- useTimeSlotDropdown.ts:80: the \`fetchParams\` computed had a switch
over the \`DropdownScenario\` type (4 string-literal cases) without
a \`default\` branch. If \`scenario.value\` ever returned a value
outside the four narrowed cases (e.g. via a future type-assertion
drift), the computed silently returned \`undefined\`, and the
consumer code (\`fetchParams.value.includeParent\`) would throw
\`Cannot read property 'includeParent' of undefined\`. Added a
\`default\` branch returning \`{ includeParent: false, includeChildren: false }\`
— same as the 'flat' case (the safest baseline: include only own
slots, no hierarchy).
E.4 — SECURITY (1× vue/no-template-target-blank):
- pages/organisation/index.vue:343: the external website anchor had
\`target='_blank'\` with \`rel='noopener'\` (only one). The rule
requires the full \`rel='noopener noreferrer'\` pair. Updated.
Mitigates reverse-tabnabbing (window.opener) AND referrer-leakage
to the linked third-party site.
E.5 — axios fire-and-forget (3× promise/no-promise-in-callback,
1 fully resolved + 2 warnings remain):
- lib/axios.ts:42: changed \`error => Promise.reject(error)\` to
\`async error => { throw error }\`. Semantically identical (axios
interceptor onRejected returns a rejected promise either way) and
satisfies the lint rule.
- lib/axios.ts:61, 73: prefixed the dynamic-import chains with \`void\`
per Q4's option-a decision (\`void import('@/stores/...').then(...)\`).
This makes the discard intent explicit, but empirically does NOT
satisfy promise/no-promise-in-callback — the rule fires on any
promise creation inside a callback, regardless of the discard
pattern. The 2 warnings remain in the post-Task-5 baseline.
Resolution path is Bert's call: either keep \`void\` and accept
the warnings as documentation, or rewrite to \`async error => {
const { useStore } = await import(...); ... }\` which sequentializes
the dynamic-import resolution with the rejection. Out of scope for
this session per the literal Q4 recipe.
Tests + typecheck verified green.
Lint baseline: 34 → 32.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -30,12 +30,13 @@ function mountWithQuery<T>(setup: () => T): { vm: { result: T }; client: QueryCl
|
||||
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } })
|
||||
|
||||
const Component = defineComponent({
|
||||
setup() {
|
||||
setup(_props, { expose }) {
|
||||
const result = setup()
|
||||
|
||||
return { result }
|
||||
expose({ result })
|
||||
|
||||
return () => h('div')
|
||||
},
|
||||
render: () => h('div'),
|
||||
})
|
||||
|
||||
const wrapper = mount(Component, {
|
||||
|
||||
@@ -88,6 +88,9 @@ export function useTimeSlotDropdown(
|
||||
|
||||
case 'cross_event':
|
||||
return { includeParent: false, includeChildren: true }
|
||||
|
||||
default:
|
||||
return { includeParent: false, includeChildren: false }
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ apiClient.interceptors.request.use(
|
||||
|
||||
return config
|
||||
},
|
||||
error => Promise.reject(error),
|
||||
async error => { throw error },
|
||||
)
|
||||
|
||||
apiClient.interceptors.response.use(
|
||||
@@ -58,7 +58,7 @@ apiClient.interceptors.response.use(
|
||||
|
||||
// Handle impersonation session expiry
|
||||
if (status === 403 && error.response?.data?.impersonation_ended) {
|
||||
import('@/stores/useImpersonationStore').then(({ useImpersonationStore }) => {
|
||||
void import('@/stores/useImpersonationStore').then(({ useImpersonationStore }) => {
|
||||
const impersonationStore = useImpersonationStore()
|
||||
|
||||
impersonationStore.clearState()
|
||||
@@ -70,7 +70,7 @@ apiClient.interceptors.response.use(
|
||||
|
||||
if (status === 401) {
|
||||
// Lazy import to avoid circular dependency
|
||||
import('@/stores/useAuthStore').then(({ useAuthStore }) => {
|
||||
void import('@/stores/useAuthStore').then(({ useAuthStore }) => {
|
||||
const authStore = useAuthStore()
|
||||
if (authStore.isInitialized)
|
||||
authStore.handleUnauthorized()
|
||||
|
||||
@@ -340,7 +340,7 @@ function describeActivity(entry: ActivityLogEntry): string {
|
||||
v-if="organisation.website"
|
||||
:href="organisation.website"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
rel="noopener noreferrer"
|
||||
class="text-body-1 text-primary"
|
||||
>{{ organisation.website }}</a>
|
||||
<span
|
||||
|
||||
Reference in New Issue
Block a user