2018-02-20 01:04:37 +00:00
|
|
|
import { ClientFunction as exec, Selector as $ } from 'testcafe'
|
2018-03-03 01:54:38 +00:00
|
|
|
import * as images from './images'
|
|
|
|
import * as blobUtils from './blobUtils'
|
2018-02-20 01:04:37 +00:00
|
|
|
|
|
|
|
export const settingsButton = $('nav a[aria-label=Settings]')
|
|
|
|
export const instanceInput = $('#instanceInput')
|
2018-04-01 05:08:24 +00:00
|
|
|
export const modalDialog = $('.modal-dialog')
|
2018-02-21 17:26:22 +00:00
|
|
|
export const modalDialogContents = $('.modal-dialog-contents')
|
|
|
|
export const closeDialogButton = $('.close-dialog-button')
|
2018-02-24 22:49:28 +00:00
|
|
|
export const notificationsNavButton = $('nav a[href="/notifications"]')
|
|
|
|
export const homeNavButton = $('nav a[href="/"]')
|
2018-03-19 17:09:05 +00:00
|
|
|
export const localTimelineNavButton = $('nav a[href="/local"]')
|
2018-03-09 02:08:14 +00:00
|
|
|
export const searchNavButton = $('nav a[href="/search"]')
|
2018-04-15 01:47:55 +00:00
|
|
|
export const communityNavButton = $('nav a[href="/community"]')
|
2018-04-21 07:33:42 +00:00
|
|
|
export const settingsNavButton = $('nav a[href="/settings"]')
|
2018-02-24 22:49:28 +00:00
|
|
|
export const formError = $('.form-error-user-error')
|
2018-02-28 05:20:48 +00:00
|
|
|
export const composeInput = $('.compose-box-input')
|
2018-03-04 00:12:48 +00:00
|
|
|
export const composeContentWarning = $('.content-warning-input')
|
2018-02-28 05:20:48 +00:00
|
|
|
export const composeButton = $('.compose-box-button')
|
|
|
|
export const composeLengthIndicator = $('.compose-box-length')
|
2018-03-01 02:45:29 +00:00
|
|
|
export const emojiButton = $('.compose-box-toolbar button:first-child')
|
2018-03-03 05:55:04 +00:00
|
|
|
export const mediaButton = $('.compose-box-toolbar button:nth-child(2)')
|
2018-03-03 21:23:26 +00:00
|
|
|
export const postPrivacyButton = $('.compose-box-toolbar button:nth-child(3)')
|
2018-03-04 00:12:48 +00:00
|
|
|
export const contentWarningButton = $('.compose-box-toolbar button:nth-child(4)')
|
2018-03-01 06:45:42 +00:00
|
|
|
export const emailInput = $('input#user_email')
|
|
|
|
export const passwordInput = $('input#user_password')
|
|
|
|
export const authorizeInput = $('button[type=submit]:not(.negative)')
|
2018-03-07 07:57:06 +00:00
|
|
|
export const logInToInstanceLink = $('a[href="/settings/instances/add"]')
|
2018-03-09 02:08:14 +00:00
|
|
|
export const searchInput = $('.search-input')
|
2018-03-10 06:31:26 +00:00
|
|
|
export const postStatusButton = $('.compose-box-button')
|
2018-03-10 18:54:16 +00:00
|
|
|
export const showMoreButton = $('.more-items-header button')
|
2018-03-15 05:14:06 +00:00
|
|
|
export const accountProfileName = $('.account-profile .account-profile-name')
|
|
|
|
export const accountProfileUsername = $('.account-profile .account-profile-username')
|
|
|
|
export const accountProfileFollowedBy = $('.account-profile .account-profile-followed-by')
|
|
|
|
export const accountProfileFollowButton = $('.account-profile .account-profile-follow button')
|
2018-03-16 03:31:58 +00:00
|
|
|
export const goBackButton = $('.dynamic-page-go-back')
|
2018-04-15 01:47:55 +00:00
|
|
|
export const accountProfileMoreOptionsButton = $('.account-profile-more-options button')
|
2018-04-21 20:06:46 +00:00
|
|
|
export const addInstanceButton = $('#submitButton')
|
|
|
|
export const mastodonLogInButton = $('button[type="submit"]')
|
2018-04-27 05:05:55 +00:00
|
|
|
export const followsButton = $('.account-profile-details > *:nth-child(2)')
|
|
|
|
export const followersButton = $('.account-profile-details > *:nth-child(3)')
|
2018-04-29 19:28:44 +00:00
|
|
|
export const avatarInComposeBox = $('.compose-box-avatar')
|
2018-08-19 22:23:40 +00:00
|
|
|
export const displayNameInComposeBox = $('.compose-box-display-name')
|
2018-08-20 01:03:26 +00:00
|
|
|
export const generalSettingsButton = $('a[href="/settings/general"]')
|
|
|
|
export const removeEmojiFromDisplayNamesInput = $('#choice-omit-emoji-in-display-names')
|
2018-02-24 22:49:28 +00:00
|
|
|
|
2018-08-26 22:38:45 +00:00
|
|
|
export const favoritesCountElement = $('.status-favs').addCustomDOMProperties({
|
2018-02-24 22:49:28 +00:00
|
|
|
innerCount: el => parseInt(el.innerText, 10)
|
|
|
|
})
|
|
|
|
|
2018-08-26 22:38:45 +00:00
|
|
|
export const reblogsCountElement = $('.status-reblogs').addCustomDOMProperties({
|
2018-02-24 22:49:28 +00:00
|
|
|
innerCount: el => parseInt(el.innerText, 10)
|
|
|
|
})
|
2018-02-20 01:04:37 +00:00
|
|
|
|
2018-03-10 18:54:16 +00:00
|
|
|
export const sleep = timeout => new Promise(resolve => setTimeout(resolve, timeout))
|
|
|
|
|
2018-02-20 01:04:37 +00:00
|
|
|
export const getUrl = exec(() => window.location.href)
|
|
|
|
|
2018-02-21 17:26:22 +00:00
|
|
|
export const getActiveElementClass = exec(() =>
|
2018-04-11 03:56:42 +00:00
|
|
|
(document.activeElement && document.activeElement.getAttribute('class')) || ''
|
2018-02-21 17:26:22 +00:00
|
|
|
)
|
|
|
|
|
2018-03-16 03:31:58 +00:00
|
|
|
export const getActiveElementInnerText = exec(() =>
|
2018-04-11 03:56:42 +00:00
|
|
|
(document.activeElement && document.activeElement.innerText) || ''
|
2018-03-16 03:31:58 +00:00
|
|
|
)
|
|
|
|
|
2018-03-17 02:04:48 +00:00
|
|
|
export const getActiveElementAriaLabel = exec(() =>
|
2018-04-11 03:56:42 +00:00
|
|
|
(document.activeElement && document.activeElement.getAttribute('aria-label')) || ''
|
2018-03-17 02:04:48 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
export const getActiveElementInsideNthStatus = exec(() => {
|
|
|
|
let element = document.activeElement
|
|
|
|
while (element) {
|
|
|
|
if (element.hasAttribute('aria-posinset')) {
|
|
|
|
return element.getAttribute('aria-posinset')
|
|
|
|
}
|
|
|
|
element = element.parentElement
|
|
|
|
}
|
2018-04-11 03:56:42 +00:00
|
|
|
return ''
|
2018-03-17 02:04:48 +00:00
|
|
|
})
|
|
|
|
|
2018-11-13 02:28:43 +00:00
|
|
|
export const getTitleText = exec(() => document.head.querySelector('title').innerHTML)
|
|
|
|
|
2018-02-21 17:26:22 +00:00
|
|
|
export const goBack = exec(() => window.history.back())
|
|
|
|
|
2018-05-26 20:51:41 +00:00
|
|
|
export const forceOffline = exec(() => window.__forceOnline(false))
|
2018-03-09 02:08:14 +00:00
|
|
|
|
2018-05-26 20:51:41 +00:00
|
|
|
export const forceOnline = exec(() => window.__forceOnline(true))
|
2018-03-09 02:08:14 +00:00
|
|
|
|
2018-03-01 02:45:29 +00:00
|
|
|
export const getComposeSelectionStart = exec(() => composeInput().selectionStart, {
|
|
|
|
dependencies: { composeInput }
|
|
|
|
})
|
|
|
|
|
2018-09-23 19:26:01 +00:00
|
|
|
export const getBodyClassList = exec(() => (
|
|
|
|
Array.prototype.slice.apply(document.body.classList).filter(_ => _ !== 'the-body'))
|
|
|
|
)
|
2018-06-07 22:26:21 +00:00
|
|
|
|
2018-03-10 18:54:16 +00:00
|
|
|
export const scrollContainerToTop = exec(() => {
|
|
|
|
document.getElementsByClassName('container')[0].scrollTop = 0
|
|
|
|
})
|
|
|
|
|
2018-03-03 05:55:04 +00:00
|
|
|
export const uploadKittenImage = i => (exec(() => {
|
|
|
|
let image = images[`kitten${i}`]
|
|
|
|
let blob = blobUtils.base64StringToBlob(image.data, 'image/png')
|
|
|
|
blob.name = image.name
|
2018-03-03 01:54:38 +00:00
|
|
|
window.__fakeFileInput(blob)
|
|
|
|
}, {
|
|
|
|
dependencies: {
|
|
|
|
images,
|
2018-03-03 05:55:04 +00:00
|
|
|
blobUtils,
|
|
|
|
i
|
2018-03-03 01:54:38 +00:00
|
|
|
}
|
2018-03-03 05:55:04 +00:00
|
|
|
}))
|
|
|
|
|
2018-04-17 16:44:28 +00:00
|
|
|
export const focus = (selector) => (exec(() => {
|
|
|
|
document.querySelector(selector).focus()
|
|
|
|
}, {
|
|
|
|
dependencies: {
|
|
|
|
selector
|
|
|
|
}
|
|
|
|
}))
|
|
|
|
|
2018-04-10 01:30:15 +00:00
|
|
|
export function getNthMediaAltInput (n) {
|
|
|
|
return $(`.compose-box .compose-media:nth-child(${n}) .compose-media-alt input`)
|
|
|
|
}
|
|
|
|
|
2018-03-30 08:06:17 +00:00
|
|
|
export function getNthComposeReplyInput (n) {
|
2018-06-09 04:54:11 +00:00
|
|
|
return $(`${getNthStatusSelector(n)} .compose-box-input`)
|
2018-03-30 08:06:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export function getNthComposeReplyButton (n) {
|
2018-06-09 04:54:11 +00:00
|
|
|
return $(`${getNthStatusSelector(n)} .compose-box-button`)
|
2018-03-30 08:06:17 +00:00
|
|
|
}
|
|
|
|
|
2018-03-30 17:04:35 +00:00
|
|
|
export function getNthPostPrivacyButton (n) {
|
2018-06-09 04:54:11 +00:00
|
|
|
return $(`${getNthStatusSelector(n)} .compose-box-toolbar button:nth-child(3)`)
|
2018-03-30 17:04:35 +00:00
|
|
|
}
|
|
|
|
|
2018-03-25 19:24:38 +00:00
|
|
|
export function getNthAutosuggestionResult (n) {
|
|
|
|
return $(`.compose-autosuggest-list-item:nth-child(${n}) button`)
|
|
|
|
}
|
|
|
|
|
2018-04-28 21:19:39 +00:00
|
|
|
export function getSearchResultByHref (href) {
|
|
|
|
return $(`.search-result a[href="${href}"]`)
|
|
|
|
}
|
|
|
|
|
2018-03-09 02:08:14 +00:00
|
|
|
export function getNthSearchResult (n) {
|
|
|
|
return $(`.search-result:nth-child(${n}) a`)
|
|
|
|
}
|
|
|
|
|
2018-03-03 05:55:04 +00:00
|
|
|
export function getNthMedia (n) {
|
|
|
|
return $(`.compose-media:nth-child(${n}) img`)
|
|
|
|
}
|
2018-03-03 01:54:38 +00:00
|
|
|
|
2018-03-03 18:27:14 +00:00
|
|
|
export function getNthDeleteMediaButton (n) {
|
|
|
|
return $(`.compose-media:nth-child(${n}) .compose-media-delete-button`)
|
|
|
|
}
|
|
|
|
|
2018-02-21 05:08:26 +00:00
|
|
|
export function getNthStatus (n) {
|
2018-04-17 16:44:28 +00:00
|
|
|
return $(getNthStatusSelector(n))
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getNthStatusSelector (n) {
|
2018-06-11 02:49:39 +00:00
|
|
|
return `.list-item > article[aria-posinset="${n}"]`
|
2018-02-21 05:08:26 +00:00
|
|
|
}
|
|
|
|
|
2018-05-26 20:51:41 +00:00
|
|
|
export function getNthStatusContent (n) {
|
|
|
|
return $(`${getNthStatusSelector(n)} .status-content`)
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getNthStatusSpoiler (n) {
|
|
|
|
return $(`${getNthStatusSelector(n)} .status-spoiler`)
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getNthStatusHeader (n) {
|
|
|
|
return $(`${getNthStatusSelector(n)} .status-header`)
|
|
|
|
}
|
|
|
|
|
2018-04-10 01:30:15 +00:00
|
|
|
export function getNthStatusAndImage (nStatus, nImage) {
|
2018-06-09 04:54:11 +00:00
|
|
|
return $(`${getNthStatusSelector(nStatus)} .status-media .show-image-button:nth-child(${nImage + 1}) img`)
|
2018-04-10 01:30:15 +00:00
|
|
|
}
|
|
|
|
|
2018-02-24 22:49:28 +00:00
|
|
|
export function getFirstVisibleStatus () {
|
2018-06-11 02:49:39 +00:00
|
|
|
return $(`.list-item > article[aria-posinset]`).nth(0)
|
2018-02-24 22:49:28 +00:00
|
|
|
}
|
|
|
|
|
2018-03-09 16:45:12 +00:00
|
|
|
export function getNthReplyButton (n) {
|
2018-06-09 04:54:11 +00:00
|
|
|
return $(`${getNthStatusSelector(n)} .status-toolbar button:nth-child(1)`)
|
2018-03-09 16:45:12 +00:00
|
|
|
}
|
|
|
|
|
2018-04-08 20:42:31 +00:00
|
|
|
export function getNthReplyContentWarningInput (n) {
|
2018-06-09 04:54:11 +00:00
|
|
|
return $(`${getNthStatusSelector(n)} .content-warning-input`)
|
2018-04-08 20:42:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export function getNthReplyContentWarningButton (n) {
|
2018-06-09 04:54:11 +00:00
|
|
|
return $(`${getNthStatusSelector(n)} .compose-box-toolbar button:nth-child(4)`)
|
2018-04-08 20:42:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export function getNthReplyPostPrivacyButton (n) {
|
2018-06-09 04:54:11 +00:00
|
|
|
return $(`${getNthStatusSelector(n)} .compose-box-toolbar button:nth-child(3)`)
|
2018-04-08 20:42:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export function getNthPostPrivacyOptionInDialog (n) {
|
|
|
|
return $(`.generic-dialog-list li:nth-child(${n}) button`)
|
|
|
|
}
|
|
|
|
|
2018-02-24 22:49:28 +00:00
|
|
|
export function getNthFavoriteButton (n) {
|
2018-06-09 04:54:11 +00:00
|
|
|
return $(`${getNthStatusSelector(n)} .status-toolbar button:nth-child(3)`)
|
2018-02-24 22:49:28 +00:00
|
|
|
}
|
|
|
|
|
2018-04-15 01:47:55 +00:00
|
|
|
export function getNthStatusOptionsButton (n) {
|
2018-06-09 04:54:11 +00:00
|
|
|
return $(`${getNthStatusSelector(n)} .status-toolbar button:nth-child(4)`)
|
2018-04-15 01:47:55 +00:00
|
|
|
}
|
|
|
|
|
2018-02-24 22:49:28 +00:00
|
|
|
export function getNthFavorited (n) {
|
|
|
|
return getNthFavoriteButton(n).getAttribute('aria-pressed')
|
|
|
|
}
|
|
|
|
|
2018-04-12 05:55:11 +00:00
|
|
|
export function getNthShowOrHideButton (n) {
|
2018-06-09 04:54:11 +00:00
|
|
|
return $(`${getNthStatusSelector(n)} .status-spoiler-button button`)
|
2018-04-12 05:55:11 +00:00
|
|
|
}
|
|
|
|
|
2018-02-24 22:49:28 +00:00
|
|
|
export function getFavoritesCount () {
|
|
|
|
return favoritesCountElement.innerCount
|
|
|
|
}
|
|
|
|
|
2018-02-25 02:20:33 +00:00
|
|
|
export function getNthReblogButton (n) {
|
2018-06-09 04:54:11 +00:00
|
|
|
return $(`${getNthStatusSelector(n)} .status-toolbar button:nth-child(2)`)
|
2018-02-25 02:20:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export function getNthReblogged (n) {
|
|
|
|
return getNthReblogButton(n).getAttribute('aria-pressed')
|
|
|
|
}
|
|
|
|
|
2018-04-15 01:47:55 +00:00
|
|
|
export function getNthDialogOptionsOption (n) {
|
|
|
|
return $(`.modal-dialog li:nth-child(${n}) button`)
|
|
|
|
}
|
|
|
|
|
2018-02-24 22:49:28 +00:00
|
|
|
export function getReblogsCount () {
|
|
|
|
return reblogsCountElement.innerCount
|
2018-02-20 01:04:37 +00:00
|
|
|
}
|
|
|
|
|
2018-06-09 04:54:11 +00:00
|
|
|
function getNthPinnedStatusSelector (n) {
|
|
|
|
return `.pinned-statuses article[aria-posinset="${n}"]`
|
|
|
|
}
|
|
|
|
|
2018-04-29 19:28:44 +00:00
|
|
|
export function getNthPinnedStatus (n) {
|
2018-06-09 04:54:11 +00:00
|
|
|
return $(getNthPinnedStatusSelector(n))
|
2018-04-29 19:28:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export function getNthPinnedStatusFavoriteButton (n) {
|
2018-06-09 04:54:11 +00:00
|
|
|
return $(`${getNthPinnedStatusSelector(n)} .status-toolbar button:nth-child(3)`)
|
2018-04-29 19:28:44 +00:00
|
|
|
}
|
|
|
|
|
2018-02-20 02:25:59 +00:00
|
|
|
export async function validateTimeline (t, timeline) {
|
2018-05-26 20:51:41 +00:00
|
|
|
const timeout = 30000
|
2018-02-20 01:04:37 +00:00
|
|
|
for (let i = 0; i < timeline.length; i++) {
|
|
|
|
let status = timeline[i]
|
2018-05-26 20:51:41 +00:00
|
|
|
// hovering forces TestCafé to scroll to that element: https://git.io/vABV2
|
|
|
|
await t.hover(getNthStatus(i))
|
2018-02-20 01:04:37 +00:00
|
|
|
if (status.content) {
|
2018-05-26 20:51:41 +00:00
|
|
|
await t.expect(getNthStatusContent(i).innerText)
|
2018-05-25 03:01:34 +00:00
|
|
|
.contains(status.content, { timeout })
|
2018-02-20 01:04:37 +00:00
|
|
|
}
|
|
|
|
if (status.spoiler) {
|
2018-05-26 20:51:41 +00:00
|
|
|
await t.expect(getNthStatusSpoiler(i).innerText)
|
2018-05-25 03:01:34 +00:00
|
|
|
.contains(status.spoiler, { timeout })
|
2018-02-20 01:04:37 +00:00
|
|
|
}
|
|
|
|
if (status.followedBy) {
|
2018-05-26 20:51:41 +00:00
|
|
|
await t.expect(getNthStatusHeader(i).innerText)
|
2018-06-09 22:04:47 +00:00
|
|
|
.match(new RegExp(status.followedBy + '\\s+followed you'), { timeout })
|
2018-02-20 01:04:37 +00:00
|
|
|
}
|
|
|
|
if (status.rebloggedBy) {
|
2018-05-26 20:51:41 +00:00
|
|
|
await t.expect(getNthStatusHeader(i).innerText)
|
2018-06-09 22:04:47 +00:00
|
|
|
.match(new RegExp(status.rebloggedBy + '\\s+boosted your status'), { timeout })
|
2018-02-20 01:04:37 +00:00
|
|
|
}
|
|
|
|
if (status.favoritedBy) {
|
2018-05-26 20:51:41 +00:00
|
|
|
await t.expect(getNthStatusHeader(i).innerText)
|
2018-06-09 22:04:47 +00:00
|
|
|
.match(new RegExp(status.favoritedBy + '\\s+favorited your status'), { timeout })
|
2018-02-20 01:04:37 +00:00
|
|
|
}
|
|
|
|
}
|
2018-02-20 02:25:59 +00:00
|
|
|
}
|
2018-02-21 05:08:26 +00:00
|
|
|
|
2018-02-24 22:49:28 +00:00
|
|
|
export async function scrollToTopOfTimeline (t) {
|
|
|
|
let i = await getFirstVisibleStatus().getAttribute('aria-posinset')
|
|
|
|
while (true) {
|
|
|
|
await t.hover(getNthStatus(i))
|
|
|
|
.expect($('.loading-footer').exist).notOk()
|
2018-05-26 20:51:41 +00:00
|
|
|
if (--i <= 0) {
|
2018-02-24 22:49:28 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-21 05:08:26 +00:00
|
|
|
export async function scrollToBottomOfTimeline (t) {
|
2018-02-24 22:49:28 +00:00
|
|
|
let i = 0
|
2018-02-21 05:08:26 +00:00
|
|
|
while (true) {
|
2018-02-24 22:49:28 +00:00
|
|
|
await t.hover(getNthStatus(i))
|
2018-02-21 05:08:26 +00:00
|
|
|
.expect($('.loading-footer').exist).notOk()
|
2018-02-24 22:49:28 +00:00
|
|
|
let size = await getNthStatus(i).getAttribute('aria-setsize')
|
2018-05-26 20:51:41 +00:00
|
|
|
if (++i >= size - 1) {
|
2018-02-21 05:08:26 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2018-02-21 05:30:16 +00:00
|
|
|
}
|
2018-02-21 17:26:22 +00:00
|
|
|
|
|
|
|
export async function scrollToStatus (t, n) {
|
2018-05-25 03:01:34 +00:00
|
|
|
let timeout = 20000
|
2018-05-26 20:51:41 +00:00
|
|
|
for (let i = 0; i <= n; i++) {
|
2018-08-30 04:42:57 +00:00
|
|
|
await t.expect(getNthStatus(i).exists).ok({ timeout })
|
2018-05-25 03:01:34 +00:00
|
|
|
.hover(getNthStatus(i))
|
2018-03-16 00:33:52 +00:00
|
|
|
.expect($('.loading-footer').exist).notOk()
|
|
|
|
if (i < n) {
|
2018-06-09 04:54:11 +00:00
|
|
|
await t.hover($(`${getNthStatusSelector(i)} .status-toolbar`))
|
2018-03-16 00:33:52 +00:00
|
|
|
.expect($('.loading-footer').exist).notOk()
|
|
|
|
}
|
2018-02-21 17:26:22 +00:00
|
|
|
}
|
|
|
|
await t.hover(getNthStatus(n))
|
|
|
|
}
|
2018-03-11 04:24:07 +00:00
|
|
|
|
|
|
|
export async function clickToNotificationsAndBackHome (t) {
|
|
|
|
await t.click(notificationsNavButton)
|
|
|
|
.expect(getUrl()).eql('http://localhost:4002/notifications')
|
|
|
|
.click(homeNavButton)
|
|
|
|
.expect(getUrl()).eql('http://localhost:4002/')
|
|
|
|
}
|
2018-04-19 03:43:13 +00:00
|
|
|
|
|
|
|
// like lodash.times but I don't want to try to figure out esm
|
|
|
|
// just to import lodash-es
|
|
|
|
export function times (n, cb) {
|
|
|
|
let arr = []
|
|
|
|
for (let i = 0; i < n; i++) {
|
|
|
|
arr.push(cb(i))
|
|
|
|
}
|
|
|
|
return arr
|
|
|
|
}
|