Browse Source

Feature: Mobile - Implement automated accessibility testing with vitest-axe.

Dusan Vuckovic 2 years ago
parent
commit
1b21908b1f

+ 51 - 0
app/frontend/apps/mobile/pages/account/__tests__/account-a11y.spec.ts

@@ -0,0 +1,51 @@
+// Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
+
+import { axe } from 'vitest-axe'
+import { visitView } from '@tests/support/components/visitView'
+import { mockAccount } from '@tests/support/mock-account'
+import { mockGraphQLApi } from '@tests/support/mock-graphql-api'
+import { AccountAvatarActiveDocument } from '../avatar/graphql/queries/active.api'
+
+const mockAvatarImage =
+  'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=='
+
+const getAvatarObject = (deletable: boolean) => {
+  return {
+    id: 'Z2lkOi8vemFtbWFkL0F2YXRhci8yNA',
+    default: true,
+    deletable,
+    initial: false,
+    imageFull: mockAvatarImage,
+    imageResize: mockAvatarImage,
+    createdAt: '2022-07-12T06:54:45Z',
+    updatedAt: '2022-07-12T06:54:45Z',
+  }
+}
+
+const mockActiveAvatar = async (deletable = true) => {
+  mockGraphQLApi(AccountAvatarActiveDocument).willResolve({
+    accountAvatarActive: getAvatarObject(deletable),
+  })
+}
+
+describe('testing account a11y', () => {
+  beforeEach(() => {
+    mockAccount({
+      lastname: 'Doe',
+      firstname: 'John',
+    })
+  })
+
+  test('account overview has no accessibility violations', async () => {
+    const view = await visitView('/account')
+    const results = await axe(view.html())
+    expect(results).toHaveNoViolations()
+  })
+
+  test('avatar editor has no accessibility violations', async () => {
+    mockActiveAvatar()
+    const view = await visitView('/account/avatar')
+    const results = await axe(view.html())
+    expect(results).toHaveNoViolations()
+  })
+})

+ 2 - 0
app/frontend/apps/mobile/pages/account/views/AccountAvatar.vue

@@ -231,6 +231,7 @@ const allowedImageTypes = computed(() => {
         data-test-id="fileGalleryInput"
         type="file"
         class="hidden"
+        aria-hidden="true"
         :accept="allowedImageTypes"
         @change="loadAvatar(fileGalleryInput)"
       />
@@ -240,6 +241,7 @@ const allowedImageTypes = computed(() => {
         data-test-id="fileCameraInput"
         type="file"
         class="hidden"
+        aria-hidden="true"
         :accept="allowedImageTypes"
         capture="environment"
         @change="loadAvatar(fileCameraInput)"

+ 12 - 0
app/frontend/apps/mobile/pages/error/__tests__/error-a11y.spec.ts

@@ -0,0 +1,12 @@
+// Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
+
+import { axe } from 'vitest-axe'
+import { visitView } from '@tests/support/components/visitView'
+
+describe('testing error a11y', () => {
+  it('has no accessibility violations', async () => {
+    const view = await visitView('/error')
+    const results = await axe(view.html())
+    expect(results).toHaveNoViolations()
+  })
+})

+ 1 - 0
app/frontend/apps/mobile/pages/error/routes.ts

@@ -13,6 +13,7 @@ const route: RouteRecordRaw = {
   meta: {
     requiresAuth: false,
     requiredPermission: null,
+    hasOwnLandmarks: true,
   },
 }
 

+ 32 - 0
app/frontend/apps/mobile/pages/home/__tests__/home-a11y.spec.ts

@@ -0,0 +1,32 @@
+// Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
+
+import { axe } from 'vitest-axe'
+import { TicketOverviewsDocument } from '@shared/entities/ticket/graphql/queries/ticket/overviews.api'
+import { visitView } from '@tests/support/components/visitView'
+import { mockAccount } from '@tests/support/mock-account'
+import createMockClient from '@tests/support/mock-apollo-client'
+import { getApiTicketOverviews } from '@tests/support/mocks/ticket-overviews'
+
+describe('testing home a11y', () => {
+  beforeEach(() => {
+    mockAccount({ id: '666' })
+    createMockClient([
+      {
+        operationDocument: TicketOverviewsDocument,
+        handler: async () => ({ data: getApiTicketOverviews() }),
+      },
+    ])
+  })
+
+  it('home screen has no accessibility violations', async () => {
+    const view = await visitView('/')
+    const results = await axe(view.html())
+    expect(results).toHaveNoViolations()
+  })
+
+  it('favorite ticket overviews screen has no accessibility violations', async () => {
+    const view = await visitView('/favorite/ticker-overviews/edit')
+    const results = await axe(view.html())
+    expect(results).toHaveNoViolations()
+  })
+})

+ 11 - 5
app/frontend/apps/mobile/pages/login/__tests__/login-a11y.spec.ts

@@ -1,13 +1,19 @@
 // Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
 
+import { axe } from 'vitest-axe'
+import { mockApplicationConfig } from '@tests/support/mock-applicationConfig'
 import { visitView } from '@tests/support/components/visitView'
 
 describe('testing login a11y', () => {
-  it('check that login view has all required landmarks', async () => {
-    const view = await visitView('/login')
+  beforeEach(() => {
+    mockApplicationConfig({
+      product_name: 'Zammad Test System',
+    })
+  })
 
-    expect(view.getByRole('main')).toBeInTheDocument() // <main />
-    expect(view.getByRole('navigation')).toBeInTheDocument() // <nav />
-    expect(view.getByRole('contentinfo')).toBeInTheDocument() // <footer />
+  it('has no accessibility violations', async () => {
+    const view = await visitView('/login')
+    const results = await axe(view.html())
+    expect(results).toHaveNoViolations()
   })
 })

+ 1 - 0
app/frontend/apps/mobile/pages/login/routes.ts

@@ -16,6 +16,7 @@ const route: RouteRecordRaw[] = [
       title: __('Sign in'),
       requiresAuth: false,
       requiredPermission: null,
+      hasOwnLandmarks: true,
     },
   },
   {

+ 76 - 0
app/frontend/apps/mobile/pages/online-notification/__tests__/online-notification-a11y.spec.ts

@@ -0,0 +1,76 @@
+// Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
+
+import { axe } from 'vitest-axe'
+import { visitView } from '@tests/support/components/visitView'
+import {
+  mockGraphQLApi,
+  mockGraphQLSubscription,
+} from '@tests/support/mock-graphql-api'
+import { OnlineNotificationsDocument } from '@shared/entities/online-notification/graphql/queries/onlineNotifications.api'
+import { OnlineNotificationsCountDocument } from '@shared/entities/online-notification/graphql/subscriptions/onlineNotificationsCount.api'
+import { waitUntil } from '@tests/support/utils'
+import { mockOnlineNotificationQuery } from '@shared/entities/online-notification/__tests__/mocks/online-notification-mocks'
+import { mockAccount } from '@tests/support/mock-account'
+
+const testNotifications: any[] = [
+  {
+    metaObject: {
+      __typename: 'Ticket',
+      id: '111',
+      internalId: 111,
+      title: 'Ticket Title 1',
+    },
+  },
+  {
+    metaObject: {
+      __typename: 'Ticket',
+      id: '222',
+      internalId: 222,
+      title: 'Ticket Title 2',
+    },
+  },
+  {
+    seen: true,
+    metaObject: {
+      __typename: 'Ticket',
+      id: '333',
+      internalId: 333,
+      title: 'Ticket Title 3',
+    },
+  },
+]
+
+describe('testing online notification a11y', () => {
+  beforeEach(async () => {
+    mockAccount({
+      firstname: 'John',
+      lastname: 'Doe',
+    })
+
+    const userUpdateSubscription = mockGraphQLSubscription(
+      OnlineNotificationsCountDocument,
+    )
+
+    await userUpdateSubscription.next({
+      data: {
+        onlineNotificationsCount: {
+          __typename: 'OnlineNotificationsCountPayload',
+          unseenCount: 2,
+        },
+      },
+    })
+  })
+
+  it('has no accessibility violations', async () => {
+    const mockApi = mockGraphQLApi(OnlineNotificationsDocument).willResolve(
+      mockOnlineNotificationQuery(testNotifications),
+    )
+
+    const view = await visitView('/notifications')
+
+    await waitUntil(() => mockApi.calls.resolve)
+
+    const results = await axe(view.html())
+    expect(results).toHaveNoViolations()
+  })
+})

+ 36 - 0
app/frontend/apps/mobile/pages/organization/__tests__/organization-a11y.spec.ts

@@ -0,0 +1,36 @@
+// Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
+
+import { axe } from 'vitest-axe'
+import { visitView } from '@tests/support/components/visitView'
+import {
+  mockGraphQLApi,
+  mockGraphQLSubscription,
+} from '@tests/support/mock-graphql-api'
+import { mockPermissions } from '@tests/support/mock-permissions'
+import { waitUntil } from '@tests/support/utils'
+import { OrganizationDocument } from '@mobile/entities/organization/graphql/queries/organization.api'
+import { OrganizationUpdatesDocument } from '@mobile/entities/organization/graphql/subscriptions/organizationUpdates.api'
+import {
+  defaultOrganization,
+  mockOrganizationObjectAttributes,
+} from '@mobile/entities/organization/__tests__/mocks/organization-mocks'
+
+describe('testing organization a11y', () => {
+  it('has no accessibility violations', async () => {
+    mockPermissions(['admin.organization'])
+
+    const organization = defaultOrganization()
+    const mockApi = mockGraphQLApi(OrganizationDocument).willResolve({
+      organization,
+    })
+    mockGraphQLSubscription(OrganizationUpdatesDocument)
+    mockOrganizationObjectAttributes()
+
+    const view = await visitView(`/organizations/${organization.internalId}`)
+
+    await waitUntil(() => mockApi.calls.resolve)
+
+    const results = await axe(view.html())
+    expect(results).toHaveNoViolations()
+  })
+})

+ 29 - 0
app/frontend/apps/mobile/pages/playground/__tests__/playground-a11y.spec.ts

@@ -0,0 +1,29 @@
+// Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
+
+import { configureAxe } from 'vitest-axe'
+import { visitView } from '@tests/support/components/visitView'
+
+describe('testing playground a11y', () => {
+  it('has no accessibility violations', async () => {
+    const view = await visitView('/playground')
+
+    const configuredAxe = configureAxe({
+      // NB: Although "Playground" is not covered by any other tests, it is prudent to run at least a violation check.
+      //   Considering this screen is used for hoisting experimental components, it might uncover accessibility issues
+      //   early in the development process. However, if you need to ignore certain rule during checks of this page,
+      //   uncomment and adjust the block below.
+      // https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#api-name-axeconfigure
+      // https://dequeuniversity.com/rules/axe/4.4
+      /*
+      rules: {
+        label: {
+          enabled: false,
+        },
+      },
+      */
+    })
+
+    const results = await configuredAxe(view.html())
+    expect(results).toHaveNoViolations()
+  })
+})

Some files were not shown because too many files changed in this diff