Skip to content

Commit

Permalink
Access token UI: Update and add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
jlledom committed Jan 23, 2025
1 parent 6036735 commit 70bf3ec
Show file tree
Hide file tree
Showing 2 changed files with 171 additions and 11 deletions.
26 changes: 15 additions & 11 deletions features/provider/admin/user/access_tokens.feature
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ Feature: Provider Admin Access tokens

Scenario: Tokens are listed in a table
Then the table should contain the following:
| Name | Scopes | Permission |
| Potato | Analytics API | Read Only |
| Banana | Billing API | Read & Write |
| Name | Scopes | Expiration | Permission |
| Potato | Analytics API | Never expires | Read Only |
| Banana | Billing API | Never expires | Read & Write |

Rule: New page
Background:
Expand All @@ -42,33 +42,37 @@ Feature: Provider Admin Access tokens
Then there is a required field "Name"
And there is a required field "Scopes"
And there is a required field "Permission"
And there is a required field "Expires in"
And the submit button is enabled

Scenario: Create access tokens without required fields
When they press "Create Access Token"
Then field "Name" has inline error "can't be blank"
And field "Scopes" has inline error "select at least one scope"
And field "Permission" has no inline error
And field "Expires in" has no inline error

Scenario: Create access token
When they press "Create Access Token"
And the form is submitted with:
| Name | LeToken |
| Analytics API | Yes |
| Permission | Read & Write |
| Name | LeToken |
| Analytics API | Yes |
| Permission | Read & Write |
| Expires in | No expiration |
Then the current page is the personal tokens page
And they should see the flash message "Access token was successfully created"
And should see the following details:
| Name | LeToken |
| Scopes | Analytics API |
| Permission | Read & Write |
| Name | LeToken |
| Scopes | Analytics API |
| Permission | Read & Write |
| Expires at | Never expires |
And there should be a link to "I have copied the token"

Rule: Edit page
Background:
Given the provider has the following access tokens:
| Name | Scopes | Permission |
| LeToken | Billing API, Analytics API | Read Only |
| Name | Scopes | Permission | Expires at |
| LeToken | Billing API, Analytics API | Read Only | 2030-01-01T00:00:00Z |
And they go to the access token's edit page

Scenario: Navigation to edit page
Expand Down
156 changes: 156 additions & 0 deletions spec/javascripts/AccessTokens/components/ExpirationDatePicker.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import { mount } from 'enzyme'

import { ExpirationDatePicker } from 'AccessTokens/components/ExpirationDatePicker'

import type { ExpirationItem, Props } from 'AccessTokens/components/ExpirationDatePicker'
import type { ReactWrapper } from 'enzyme'

const msExp = /\.\d{3}Z$/

const defaultProps: Props = {
id: 'expires_at',
label: 'Expires in',
tzOffset: 0
}

const mountWrapper = (props: Partial<Props> = {}) => mount(<ExpirationDatePicker {...{ ...defaultProps, ...props }} />)

const selectItem = (wrapper: ReactWrapper<any, Readonly<object>>, item: ExpirationItem) => {
wrapper.find('select.pf-c-form-control-expiration option').forEach((op) => {
const elem: HTMLOptionElement = op.getDOMNode()
elem.selected = elem.value === item.id // Mark option as selected if it's value matches the given one
})
wrapper.find('select.pf-c-form-control-expiration').simulate('change')
}

const pickDate = (wrapper: ReactWrapper<any, Readonly<object>>) => {
/*
* Pick tomorrow, to do so, we get the date selected by default which is today and click the next one.
* It could happen that today is the last day in the calendar, in that case we pick the previous day, yesterday.
* In any case, we return the picked date to the caller.
*/
const targetDate = new Date()
targetDate.setHours(0)
targetDate.setMinutes(0)
targetDate.setSeconds(0)
targetDate.setMilliseconds(0)

const tomorrowButton = wrapper.find('.pf-m-selected > button')

tomorrowButton.simulate('click')
targetDate.setDate(targetDate.getDate() + 1)
return targetDate
}

const dateFormatter = Intl.DateTimeFormat('en-US', {
month: 'long', day: 'numeric', year: 'numeric', hour: 'numeric', minute: 'numeric', hour12: false
})

it('should render itself', () => {
const wrapper = mountWrapper()
expect(wrapper.exists()).toEqual(true)
})

describe('select a period', () => {
const targetItem: ExpirationItem = { id: '90', label: '90 days', period: 90 }

it('should update hint to the correct date', () => {
const wrapper = mountWrapper()
const targetDate = new Date(new Date().getTime() + 1000 * 60 * 60 * 24 * targetItem.period)
const expectedHint = `The token will expire on ${dateFormatter.format(targetDate)}`

selectItem(wrapper, targetItem)
const hint = wrapper.find('.pf-c-form__helper-text').text()

expect(hint).toBe(expectedHint)
})

it('should update hidden input value to the correct timestamp', () => {
const wrapper = mountWrapper()
const targetDate = new Date(new Date().getTime() + 1000 * 60 * 60 * 24 * targetItem.period)
const expectedValue = targetDate.toISOString().replace(msExp, 'Z')

selectItem(wrapper, targetItem)
const value = (wrapper.find(`input#${defaultProps.id}`).prop('value') as string).replace(msExp, 'Z')

expect(value).toBe(expectedValue)
})
})

describe('select "Custom"', () => {
const targetItem: ExpirationItem = { id: 'custom', label: 'Custom...', period: 0 }

it('should show a calendar', () => {
const wrapper = mountWrapper()

selectItem(wrapper, targetItem)
const calendar = wrapper.find('.pf-c-calendar-month')

expect(calendar.exists()).toBe(true)
})

describe('pick a date from the calendar', () => {
it('should update hint to the correct date', () => {
const wrapper = mountWrapper()

selectItem(wrapper, targetItem)
const targetDate = pickDate(wrapper)
const expectedHint = `The token will expire on ${dateFormatter.format(targetDate)}`
const hint = wrapper.find('.pf-c-form__helper-text').text()

expect(hint).toBe(expectedHint)
})

it('should update hidden input value to the correct timestamp', () => {
const wrapper = mountWrapper()

selectItem(wrapper, targetItem)
const targetDate = pickDate(wrapper)
const expectedValue = targetDate.toISOString().replace(msExp, 'Z')
const value = (wrapper.find(`input#${defaultProps.id}`).prop('value') as string).replace(msExp, 'Z')

expect(value).toBe(expectedValue)
})
})
})

describe('select "No expiration"', () => {
const targetItem: ExpirationItem = { id: 'no-exp', label: 'No expiration', period: 0 }

it('should show a warning', () => {
const wrapper = mountWrapper()

selectItem(wrapper, targetItem)
const warning = wrapper.find('.pf-c-alert.pf-m-warning')

expect(warning.exists()).toBe(true)
})

it('should update hidden input value to empty', () => {
const wrapper = mountWrapper()
const expectedValue = ''

selectItem(wrapper, targetItem)
const value = wrapper.find(`input#${defaultProps.id}`).prop('value')

expect(value).toBe(expectedValue)
})
})

describe('time zone matches', () => {
it('should not show a warning', ()=> {
jest.spyOn(Date.prototype, 'getTimezoneOffset').mockImplementation(() => (0))
const wrapper = mountWrapper()

expect(wrapper.exists('.pf-c-form__group-label-help')).toEqual(false)
})
})

describe('time zone mismatches', () => {
it('should show a warning', ()=> {
jest.spyOn(Date.prototype, 'getTimezoneOffset').mockImplementation(() => (-120))
const wrapper = mountWrapper()

expect(wrapper.exists('.pf-c-form__group-label-help')).toEqual(true)
})
})

0 comments on commit 70bf3ec

Please sign in to comment.