Setup rooibos test framework (#1141)

This commit is contained in:
Charles Ewert 2023-04-22 09:03:44 -04:00 committed by GitHub
parent b563ff4110
commit c4d5a7de0a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 2397 additions and 7653 deletions

17
.gitignore vendored
View File

@ -1,18 +1,17 @@
*.svg
jellyfin-roku.zip
source/globals.brs
.env
###BrightScript specific
# BrightScript
dist/apps
out/
build/
roku_modules
#NPM modules
source/globals.brs
jellyfin-roku.zip
# Rooibos
bsconfig-tdd.json
# NPM
node_modules/
#Eclipse
# Eclipse
.buildpath
.project
.settings

418
.vscode/brighterscript.code-snippets vendored Normal file
View File

@ -0,0 +1,418 @@
{
"rooibos beforeEach": {
"prefix": "beforeEach",
"body": [
"@beforeEach",
"function ${2:namespace}_${3:itGroup}_beforeEach()",
"\t$0",
"end function"
]
},
"rooibos afterEach": {
"prefix": "afterEach",
"body": [
"@afterEach",
"function ${2:namespace}_${3:itGroup}_afterEach()",
"\t$0",
"end function"
]
},
"rooibos setup": {
"prefix": "setup",
"body": [
"@setup",
"function ${2:namespace}_setup()",
"\t$0",
"end function"
]
},
"rooibos tearDown": {
"prefix": "tearDown",
"body": [
"@tearDown",
"function ${2:namespace}_tearDown()",
"\t$0",
"end function"
]
},
"rooibos ignore": {
"prefix": "ignore",
"body": [
"@ignore ${1:reason}",
"$0"
]
},
"rooibos only": {
"prefix": "only",
"body": [
"@only",
"$0"
]
},
"rooibos testSuite": {
"prefix": "suite",
"body": [
"@suite(\"$1\")",
"$0"
]
},
"rooibos testcase": {
"prefix": "it",
"body": [
"@it(\"$1\")",
"function _()",
"\t$0",
"end function"
]
},
"rooibos params": {
"prefix": "params",
"body": [
"@params(${1:values})$0"
]
},
"rooibos it": {
"prefix": "describe",
"body": [
"'+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++",
"@describe(\"${1:groupName}\")",
"'+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++",
"",
"$0"
]
},
"rooibos stub": {
"prefix": "stub",
"body": [
"m.stub(${1:target}, \"${2:methodName}\", [${3:methodArgs}], ${4:result})",
"$0"
]
},
"rooibos mock": {
"prefix": "expect",
"body": [
"${1:mockName} = m.mock(${2:target}, \"${3:methodName}\", ${4:expectedNumberOfcalls}, [${5:methodArgs}], ${6:result})",
"$0"
]
},
"rooibos expect": {
"prefix": "expect",
"body": [
"m.expectOnce(${1:target}, \"${2:methodName}\", ${3:expectedNumberOfcalls}, [${4:methodArgs}], ${5:result})",
"$0"
]
},
"rooibos expectOnce": {
"prefix": "expectOnce",
"body": [
"m.expectOnce(${1:target}, \"${2:methodName}\", [${3:methodArgs}], ${4:result})",
"$0"
]
},
"rooibos expectCallfunc": {
"prefix": "expectCallfunc",
"body": [
"m.expectOnce(${1:target}, \"callFunc\", [\"${2:methodName}\", ${3:methodArgs}], ${4:result})",
"$0"
]
},
"rooibos expectObserveNodeField": {
"prefix": "eonf",
"body": [
"m.expectOnce(${1:target}, \"observeNodeField\", [${2:node},\"${3:fieldName}\", m.${4:callback}])",
"$0"
]
},
"rooibos expectUnObserveNodeField": {
"prefix": "eunf",
"body": [
"m.expectOnce(${1:target}, \"unobserveNodeField\", [${2:node},\"${:fieldName}\", m.${4:callback}])",
"$0"
]
},
"rooibos expectObjectOnce": {
"prefix": "expectObjectOnce",
"body": [
"${1:name} = { \"id\" : \"${1:name}\" }",
"m.expectOnce(${2:target}, \"${3:methodName}\", [${4:methodArgs}], ${1:name})",
"$0"
]
},
"rooibos expectGetInstance": {
"prefix": "expectGetInstance",
"body": [
"${1:name} = { \"id\" : \"${1:name}\" }",
"m.expectOnce(${2:target}, \"getInstance\", [\"${3:instanceName}\"], ${1:name})",
"$0"
]
},
"rooibos expectCreateSGNode": {
"prefix": "expectCreateSGNode",
"body": [
"${1:name} = { \"id\" : \"${1:name}\" }",
"m.expectOnce(${2:target}, \"createSGNode\", [\"${3:nodeType}\"$0], ${1:name})"
]
},
"rooibos expectGetClassInstance": {
"prefix": "expectGetClassInstance",
"body": [
"${1:name} = { \"id\" : \"${1:name}\" }",
"m.expectOnce(${2:target}, \"getClassInstance\", [\"${3:instanceName}\"], ${1:name})",
"$0"
]
},
"rooibos expectExpectOnce": {
"prefix": "expectExpect",
"body": [
"${1:name} = { \"id\" : \"${1:name}\" }",
"m.expectOnce(${2:target}, \"${3:methodName}\", [${4:methodArgs}], ${1:name})",
"m.expectOnce(${1:name}, \"${5:methodName}\", [${6:methodArgs}], ${7:name})",
"$0"
]
},
"rooibos expectNone": {
"prefix": "expectNone",
"body": [
"m.expectNone(${1:target}, \"${2:methodName}\")",
"$0"
]
},
"rooibos assertFalse": {
"prefix": "assertFalse",
"body": [
"m.assertFalse(${1:value})",
"$0"
]
},
"rooibos assertAsync": {
"prefix": "assertAsync",
"body": [
"m.AssertAsyncField(${1:value}, $2{:fieldName})",
"$0"
]
},
"rooibos assertTrue": {
"prefix": "assertTrue",
"body": [
"m.assertTrue(${1:value})",
"$0"
]
},
"rooibos assertEqual": {
"prefix": "assertEqual",
"body": [
"m.assertEqual(${1:value}, ${2:expected})",
"$0"
]
},
"rooibos assertLike": {
"prefix": "assertLike",
"body": [
"m.assertLike(${1:value}, ${2:expected})",
"$0"
]
},
"rooibos assertNotEqual": {
"prefix": "assertNotEqual",
"body": [
"m.assertNotEqual(${1:value}, ${2:expected})",
"$0"
]
},
"rooibos assertInvalid": {
"prefix": "assertInvalid",
"body": [
"m.assertInvalid(${1:value})",
"$0"
]
},
"rooibos assertNotInvalid": {
"prefix": "assertNotInvalid",
"body": [
"m.assertNotInvalid(${1:value})",
"$0"
]
},
"rooibos assertAAHasKey": {
"prefix": "assertAAHasKey",
"body": [
"m.assertAAHasKey(${1:value}, ${2:expected})",
"$0"
]
},
"rooibos assertAANotHasKey": {
"prefix": "assertAANotHasKey",
"body": [
"m.assertAANotHasKey(${1:value}, ${2:expected})",
"$0"
]
},
"rooibos assertAAHasKeys": {
"prefix": "assertAAHasKeys",
"body": [
"m.assertAAHasKeys(${1:value}, ${2:expected})",
"$0"
]
},
"rooibos assertAANotHasKeys": {
"prefix": "assertAANotHasKeys",
"body": [
"m.assertAANotHasKeys(${1:value}, ${2:expected})",
"$0"
]
},
"rooibos assertArrayContains": {
"prefix": "assertArrayContains",
"body": [
"m.assertArrayContains(${1:value}, ${2:expected})",
"$0"
]
},
"rooibos assertArrayNotContains": {
"prefix": "assertArrayNotContains",
"body": [
"m.assertArrayNotContains(${1:value}, ${2:expected})",
"$0"
]
},
"rooibos assertArrayContainsSubset": {
"prefix": "assertArrayContainsSubset",
"body": [
"m.assertArrayContainsSubset(${1:value}, ${2:expected})",
"$0"
]
},
"rooibos assertArrayContainsAAs": {
"prefix": "assertArrayContainsAAs",
"body": [
"m.assertArrayContainsAAs(${1:value}, ${2:expected})",
"$0"
]
},
"rooibos assertArrayNotContainsSubset": {
"prefix": "assertArrayNotContainsSubset",
"body": [
"m.assertArrayNotContainsSubset(${1:value}, ${2:expected})",
"$0"
]
},
"rooibos assertArrayCount": {
"prefix": "assertArrayCount",
"body": [
"m.assertArrayCount(${1:value}, ${2:expected})",
"$0"
]
},
"rooibos assertArrayNotCount": {
"prefix": "assertArrayNotCount",
"body": [
"m.assertArrayNotCount(${1:value}, ${2:expected})",
"$0"
]
},
"rooibos assertEmpty": {
"prefix": "assertEmpty",
"body": [
"m.assertEmpty(${1:value})",
"$0"
]
},
"rooibos assertNotEmpty": {
"prefix": "assertNotEmpty",
"body": [
"m.assertNotEmpty(${1:value})",
"$0"
]
},
"rooibos assertArrayContainsOnlyValuesOfType": {
"prefix": "assertArrayContainsOnlyValuesOfType",
"body": [
"m.assertArrayContainsOnlyValuesOfType(${1:value}, ${2:expected})",
"$0"
]
},
"rooibos assertType": {
"prefix": "assertType",
"body": [
"m.assertType(${1:value}, ${2:expected})",
"$0"
]
},
"rooibos assertSubType": {
"prefix": "assertSubType",
"body": [
"m.assertSubType(${1:value}, ${2:expected})",
"$0"
]
},
"rooibos assertNodeCount": {
"prefix": "assertNodeCount",
"body": [
"m.assertNodeCount(${1:value}, ${2:expected})",
"$0"
]
},
"rooibos assertNodeNotCount": {
"prefix": "assertNodeNotCount",
"body": [
"m.assertNodeNotCount(${1:value}, ${2:expected})",
"$0"
]
},
"rooibos assertNodeEmpty": {
"prefix": "assertNodeEmpty",
"body": [
"m.assertNodeEmpty(${1:value})",
"$0"
]
},
"rooibos assertNodeNotEmpty": {
"prefix": "assertNodeNotEmpty",
"body": [
"m.assertNodeNotEmpty(${1:value})",
"$0"
]
},
"rooibos assertNodeContains": {
"prefix": "assertNodeContains",
"body": [
"m.assertNodeContains(${1:value}, ${2:expected})",
"$0"
]
},
"rooibos assertNodeNotContains": {
"prefix": "assertNodeNotContains",
"body": [
"m.assertNodeNotContains(${1:value}, ${2:expected})",
"$0"
]
},
"rooibos assertNodeContainsFields": {
"prefix": "assertNodeContainsFields",
"body": [
"m.assertNodeContainsFields(${1:value}, ${2:expected})",
"$0"
]
},
"rooibos assertNodeNotContainsFields": {
"prefix": "assertNodeNotContainsFields",
"body": [
"m.assertNodeNotContainsFields(${1:value}, ${2:expected})",
"$0"
]
},
"rooibos assertAAContainsSubset": {
"prefix": "assertAAContainsSubset",
"body": [
"m.assertAAContainsSubset(${1:value}, ${2:expected})",
"$0"
]
},
"rooibos assertMocks": {
"prefix": "assertMocks",
"body": [
"m.assertMocks(${1:value}, ${2:expected})",
"$0"
]
}
}

62
.vscode/launch.json vendored
View File

@ -4,7 +4,7 @@
{
"type": "brightscript",
"request": "launch",
"name": "Jellyfin Debug: Launch",
"name": "Jellyfin Debug",
"stopOnEntry": false,
// To enable RALE:
// set "brightscript.debug.raleTrackerTaskFileLocation": "/absolute/path/to/rale/TrackerTask.xml" in your vscode user settings
@ -22,6 +22,66 @@
"source/**/*",
"manifest"
]
},
{
"name": "Run tests",
"type": "brightscript",
"request": "launch",
"consoleOutput": "full",
"internalConsoleOptions": "neverOpen",
"preLaunchTask": "build-tests",
"retainStagingFolder": true,
"stopOnEntry": false,
"files": [
"!**/images/*.*",
"!**/fonts/*.*",
"!*.jpg",
"!*.png",
"*",
"*.*",
"**/*.*",
"!*.zip",
"!**/*.zip"
],
"rootDir": "${workspaceFolder}/build",
"sourceDirs": [
"${workspaceFolder}/test-app"
],
"enableDebuggerAutoRecovery": true,
"stopDebuggerOnAppExit": true,
"enableVariablesPanel": false,
"injectRaleTrackerTask": false,
"enableDebugProtocol": false
},
{
"name": "Run test-tdd",
"type": "brightscript",
"request": "launch",
"consoleOutput": "full",
"internalConsoleOptions": "neverOpen",
"preLaunchTask": "build-tdd",
"retainStagingFolder": true,
"stopOnEntry": false,
"files": [
"!**/images/*.*",
"!**/fonts/*.*",
"!*.jpg",
"!*.png",
"*",
"*.*",
"**/*.*",
"!*.zip",
"!**/*.zip"
],
"rootDir": "${workspaceFolder}/build",
"sourceDirs": [
"${workspaceFolder}/test-app"
],
"enableDebuggerAutoRecovery": true,
"stopDebuggerOnAppExit": true,
"enableVariablesPanel": false,
"injectRaleTrackerTask": false,
"enableDebugProtocol": false
}
]
}

View File

@ -9,5 +9,6 @@
"editor.defaultFormatter": "DavidAnson.vscode-markdownlint"
},
"xml.format.maxLineWidth": 0,
"editor.formatOnSave": true
"editor.formatOnSave": true,
"brightscript.bsdk": "node_modules/brighterscript"
}

41
.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,41 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build-tests",
"type": "shell",
"command": "npm run build-tests",
"problemMatcher": [],
"presentation": {
"echo": true,
"reveal": "silent",
"focus": false,
"panel": "shared",
"showReuseMessage": false,
"clear": true
},
"group": {
"kind": "build",
"isDefault": true
}
},
{
"label": "build-tdd",
"type": "shell",
"command": "npm run build-tdd",
"problemMatcher": [],
"presentation": {
"echo": true,
"reveal": "silent",
"focus": false,
"panel": "shared",
"showReuseMessage": false,
"clear": true
},
"group": {
"kind": "build",
"isDefault": true
}
}
]
}

44
bsconfig-tests.json Normal file
View File

@ -0,0 +1,44 @@
{
"files": [
{
"src": "test-app/**/*"
},
{
"src": "source/**/!(Main.brs)",
"dest": "source"
},
{
"src": "components/**/*",
"dest": "components"
},
{
"src": "locale/**/*",
"dest": "locale"
},
{
"src": "settings/**/*",
"dest": "settings"
}
],
"autoImportComponentScript": true,
"createPackage": false,
"stagingFolderPath": "build",
"plugins": [
"rooibos-roku"
],
"rooibos": {
"isRecordingCodeCoverage": false,
"testsFilePattern": null,
"tags": [
"!integration",
"!deprecated",
"!fixme"
],
"showOnlyFailures": true,
"catchCrashes": true,
"lineWidth": 70,
"failFast": false,
"sendHomeOnFinish": false
},
"sourceMap": true
}

View File

@ -9,11 +9,11 @@
"settings/*.*"
],
"plugins": [
"@rokucommunity/bslint"
"@rokucommunity/bslint",
"rooibos-roku"
],
"diagnosticFilters": [
"**/roku_modules/**/*",
"**/testFramework/*",
"**/tests/*"
"node_modules/**",
"**/roku_modules/**"
]
}

View File

@ -1,6 +1,9 @@
{
"files": [
"source/**/*.brs",
"components/**/*.brs"
"source/**/*.bs",
"components/**/*.brs",
"components/**/*.bs",
"test-app/**/*.bs"
]
}
}

5929
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -2,29 +2,42 @@
"name": "jellyfin-roku",
"version": "1.6.5",
"description": "Roku app for Jellyfin media server",
"main": "index.js",
"dependencies": {
"api": "npm:jellyfin-api-bs-client@1.0.5",
"bgv": "npm:button-group-vert@1.0.2",
"brighterscript-formatter": "1.6.24",
"intKeyboard": "npm:integer-keyboard@1.0.12",
"sob": "npm:slide-out-button@1.0.1"
},
"devDependencies": {
"@rokucommunity/bslint": "0.8.3",
"brighterscript": "0.64.2",
"ropm": "0.10.13",
"jshint": "^2.13.6",
"bslib": "npm:@rokucommunity/bslib@0.1.1",
"jshint": "2.13.6",
"markdownlint-cli2": "0.7.0",
"spellchecker-cli": "6.1.1"
"rimraf": "4.4.1",
"roku-deploy": "3.10.0",
"roku-log-bsc-plugin": "0.7.0",
"rooibos-roku": "5.4.2",
"ropm": "0.10.13",
"spellchecker-cli": "6.1.1",
"undent": "0.1.0"
},
"scripts": {
"postinstall": "npx ropm copy",
"validate": "npx bsc --copy-to-staging=false --create-package=false",
"test": "echo \"Error: no test specified\" && exit 1",
"build-tests": "npx rimraf build/ && npx bsc --project bsconfig-tests.json",
"build-tdd": "npx rimraf build/ && npx bsc --project bsconfig-tdd.json",
"check-formatting": "npx bsfmt --check",
"format": "npx bsfmt --write",
"lint": "bslint",
"lint-json": "jshint --extra-ext .json --verbose --exclude node_modules ./",
"lint-markdown": "markdownlint-cli2 \"**/*.md\" \"#node_modules\"",
"lint-spelling": "spellchecker -d dictionary.txt --files \"**/*.md\" \"**/.*/**/*.md\" \"!node_modules/**/*.md\"",
"check-formatting": "npx bsfmt --check",
"format": "npx bsfmt --write"
"postinstall": "npx ropm copy",
"validate": "npx bsc --copy-to-staging=false --create-package=false"
},
"repository": {
"type": "git",
"url": "git+https://github.com/jellyfin/jellyfin-roku.git"
"url": "https://github.com/jellyfin/jellyfin-roku.git"
},
"keywords": [
"jellyfin",
@ -35,12 +48,5 @@
"bugs": {
"url": "https://github.com/jellyfin/jellyfin-roku/issues"
},
"homepage": "https://github.com/jellyfin/jellyfin-roku#readme",
"dependencies": {
"api": "npm:jellyfin-api-bs-client@^1.0.5",
"bgv": "npm:button-group-vert@^1.0.2",
"brighterscript-formatter": "^1.6.8",
"sob": "npm:slide-out-button@^1.0.1",
"intKeyboard": "npm:integer-keyboard@^1.0.12"
}
"homepage": "https://github.com/jellyfin/jellyfin-roku#readme"
}

View File

@ -1,26 +1,6 @@
sub Main (args as dynamic) as void
appInfo = CreateObject("roAppInfo")
if appInfo.IsDev() and args.RunTests = "true" and TF_Utils__IsFunction(TestRunner)
' POST to {ROKU ADDRESS}:8060/launch/dev?RunTests=true
Runner = TestRunner()
Runner.SetFunctions([
TestSuite__Misc
])
Runner.Logger.SetVerbosity(1)
Runner.Logger.SetEcho(false)
Runner.Logger.SetJUnit(false)
Runner.SetFailFast(true)
Runner.Run()
end if
' The main function that runs when the application is launched.
m.screen = CreateObject("roSGScreen")
' Set global constants
setConstants()
' Write screen tracker for screensaver
@ -77,6 +57,7 @@ sub Main (args as dynamic) as void
end if
' Only show the Whats New popup the first time a user runs a new client version.
appInfo = CreateObject("roAppInfo")
if appInfo.GetVersion() <> get_setting("LastRunVersion")
' Ensure the user hasn't disabled Whats New popups
if get_user_setting("load.allowwhatsnew") = "true"
@ -621,149 +602,3 @@ sub Main (args as dynamic) as void
end while
end sub
function LoginFlow(startOver = false as boolean)
'Collect Jellyfin server and user information
start_login:
if get_setting("server") = invalid then startOver = true
invalidServer = true
if not startOver
' Show Connecting to Server spinner
dialog = createObject("roSGNode", "ProgressDialog")
dialog.title = tr("Connecting to Server")
m.scene.dialog = dialog
invalidServer = ServerInfo().Error
dialog.close = true
end if
m.serverSelection = "Saved"
if startOver or invalidServer
print "Get server details"
SendPerformanceBeacon("AppDialogInitiate") ' Roku Performance monitoring - Dialog Starting
m.serverSelection = CreateServerGroup()
SendPerformanceBeacon("AppDialogComplete") ' Roku Performance monitoring - Dialog Closed
if m.serverSelection = "backPressed"
print "backPressed"
m.global.sceneManager.callFunc("clearScenes")
return false
end if
SaveServerList()
end if
if get_setting("active_user") = invalid
SendPerformanceBeacon("AppDialogInitiate") ' Roku Performance monitoring - Dialog Starting
publicUsers = GetPublicUsers()
if publicUsers.count()
publicUsersNodes = []
for each item in publicUsers
user = CreateObject("roSGNode", "PublicUserData")
user.id = item.Id
user.name = item.Name
if item.PrimaryImageTag <> invalid
user.ImageURL = UserImageURL(user.id, { "tag": item.PrimaryImageTag })
end if
publicUsersNodes.push(user)
end for
userSelected = CreateUserSelectGroup(publicUsersNodes)
if userSelected = "backPressed"
SendPerformanceBeacon("AppDialogComplete") ' Roku Performance monitoring - Dialog Closed
return LoginFlow(true)
else
'Try to login without password. If the token is valid, we're done
get_token(userSelected, "")
if get_setting("active_user") <> invalid
m.user = AboutMe()
LoadUserPreferences()
LoadUserAbilities(m.user)
SendPerformanceBeacon("AppDialogComplete") ' Roku Performance monitoring - Dialog Closed
return true
end if
end if
else
userSelected = ""
end if
passwordEntry = CreateSigninGroup(userSelected)
SendPerformanceBeacon("AppDialogComplete") ' Roku Performance monitoring - Dialog Closed
if passwordEntry = "backPressed"
m.global.sceneManager.callFunc("clearScenes")
return LoginFlow(true)
end if
end if
m.user = AboutMe()
if m.user = invalid or m.user.id <> get_setting("active_user")
print "Login failed, restart flow"
unset_setting("active_user")
goto start_login
end if
LoadUserPreferences()
LoadUserAbilities(m.user)
m.global.sceneManager.callFunc("clearScenes")
'Send Device Profile information to server
body = getDeviceCapabilities()
req = APIRequest("/Sessions/Capabilities/Full")
req.SetRequest("POST")
postJson(req, FormatJson(body))
return true
end function
sub SaveServerList()
'Save off this server to our list of saved servers for easier navigation between servers
server = get_setting("server")
saved = get_setting("saved_servers")
if server <> invalid
server = LCase(server)'Saved server data is always lowercase
end if
entryCount = 0
addNewEntry = true
savedServers = { serverList: [] }
if saved <> invalid
savedServers = ParseJson(saved)
entryCount = savedServers.serverList.Count()
if savedServers.serverList <> invalid and entryCount > 0
for each item in savedServers.serverList
if item.baseUrl = server
addNewEntry = false
exit for
end if
end for
end if
end if
if addNewEntry
if entryCount = 0
set_setting("saved_servers", FormatJson({ serverList: [{ name: m.serverSelection, baseUrl: server, iconUrl: "pkg:/images/logo-icon120.jpg", iconWidth: 120, iconHeight: 120 }] }))
else
savedServers.serverList.Push({ name: m.serverSelection, baseUrl: server, iconUrl: "pkg:/images/logo-icon120.jpg", iconWidth: 120, iconHeight: 120 })
set_setting("saved_servers", FormatJson(savedServers))
end if
end if
end sub
sub DeleteFromServerList(urlToDelete)
saved = get_setting("saved_servers")
if urlToDelete <> invalid
urlToDelete = LCase(urlToDelete)
end if
if saved <> invalid
savedServers = ParseJson(saved)
newServers = { serverList: [] }
for each item in savedServers.serverList
if item.baseUrl <> urlToDelete
newServers.serverList.Push(item)
end if
end for
set_setting("saved_servers", FormatJson(newServers))
end if
end sub
' Roku Performance monitoring
sub SendPerformanceBeacon(signalName as string)
if m.global.app_loaded = false
m.scene.signalBeacon(signalName)
end if
end sub

View File

@ -1,3 +1,149 @@
function LoginFlow(startOver = false as boolean)
'Collect Jellyfin server and user information
start_login:
if get_setting("server") = invalid then startOver = true
invalidServer = true
if not startOver
' Show Connecting to Server spinner
dialog = createObject("roSGNode", "ProgressDialog")
dialog.title = tr("Connecting to Server")
m.scene.dialog = dialog
invalidServer = ServerInfo().Error
dialog.close = true
end if
m.serverSelection = "Saved"
if startOver or invalidServer
print "Get server details"
SendPerformanceBeacon("AppDialogInitiate") ' Roku Performance monitoring - Dialog Starting
m.serverSelection = CreateServerGroup()
SendPerformanceBeacon("AppDialogComplete") ' Roku Performance monitoring - Dialog Closed
if m.serverSelection = "backPressed"
print "backPressed"
m.global.sceneManager.callFunc("clearScenes")
return false
end if
SaveServerList()
end if
if get_setting("active_user") = invalid
SendPerformanceBeacon("AppDialogInitiate") ' Roku Performance monitoring - Dialog Starting
publicUsers = GetPublicUsers()
if publicUsers.count()
publicUsersNodes = []
for each item in publicUsers
user = CreateObject("roSGNode", "PublicUserData")
user.id = item.Id
user.name = item.Name
if item.PrimaryImageTag <> invalid
user.ImageURL = UserImageURL(user.id, { "tag": item.PrimaryImageTag })
end if
publicUsersNodes.push(user)
end for
userSelected = CreateUserSelectGroup(publicUsersNodes)
if userSelected = "backPressed"
SendPerformanceBeacon("AppDialogComplete") ' Roku Performance monitoring - Dialog Closed
return LoginFlow(true)
else
'Try to login without password. If the token is valid, we're done
get_token(userSelected, "")
if get_setting("active_user") <> invalid
m.user = AboutMe()
LoadUserPreferences()
LoadUserAbilities(m.user)
SendPerformanceBeacon("AppDialogComplete") ' Roku Performance monitoring - Dialog Closed
return true
end if
end if
else
userSelected = ""
end if
passwordEntry = CreateSigninGroup(userSelected)
SendPerformanceBeacon("AppDialogComplete") ' Roku Performance monitoring - Dialog Closed
if passwordEntry = "backPressed"
m.global.sceneManager.callFunc("clearScenes")
return LoginFlow(true)
end if
end if
m.user = AboutMe()
if m.user = invalid or m.user.id <> get_setting("active_user")
print "Login failed, restart flow"
unset_setting("active_user")
goto start_login
end if
LoadUserPreferences()
LoadUserAbilities(m.user)
m.global.sceneManager.callFunc("clearScenes")
'Send Device Profile information to server
body = getDeviceCapabilities()
req = APIRequest("/Sessions/Capabilities/Full")
req.SetRequest("POST")
postJson(req, FormatJson(body))
return true
end function
sub SaveServerList()
'Save off this server to our list of saved servers for easier navigation between servers
server = get_setting("server")
saved = get_setting("saved_servers")
if server <> invalid
server = LCase(server)'Saved server data is always lowercase
end if
entryCount = 0
addNewEntry = true
savedServers = { serverList: [] }
if saved <> invalid
savedServers = ParseJson(saved)
entryCount = savedServers.serverList.Count()
if savedServers.serverList <> invalid and entryCount > 0
for each item in savedServers.serverList
if item.baseUrl = server
addNewEntry = false
exit for
end if
end for
end if
end if
if addNewEntry
if entryCount = 0
set_setting("saved_servers", FormatJson({ serverList: [{ name: m.serverSelection, baseUrl: server, iconUrl: "pkg:/images/logo-icon120.jpg", iconWidth: 120, iconHeight: 120 }] }))
else
savedServers.serverList.Push({ name: m.serverSelection, baseUrl: server, iconUrl: "pkg:/images/logo-icon120.jpg", iconWidth: 120, iconHeight: 120 })
set_setting("saved_servers", FormatJson(savedServers))
end if
end if
end sub
sub DeleteFromServerList(urlToDelete)
saved = get_setting("saved_servers")
if urlToDelete <> invalid
urlToDelete = LCase(urlToDelete)
end if
if saved <> invalid
savedServers = ParseJson(saved)
newServers = { serverList: [] }
for each item in savedServers.serverList
if item.baseUrl <> urlToDelete
newServers.serverList.Push(item)
end if
end for
set_setting("saved_servers", FormatJson(newServers))
end if
end sub
' Roku Performance monitoring
sub SendPerformanceBeacon(signalName as string)
if m.global.app_loaded = false
m.scene.signalBeacon(signalName)
end if
end sub
function CreateServerGroup()
screen = CreateObject("roSGNode", "SetServerScreen")
screen.optionsAvailable = true

File diff suppressed because it is too large Load Diff

View File

@ -1,81 +0,0 @@
function TestSuite__Misc() as object
' Inherite test suite from BaseTestSuite
this = BaseTestSuite()
' Test suite name for log statistics
this.Name = "MiscTestSuite"
this.SetUp = MiscTestSuite__SetUp
this.TearDown = MiscTestSuite__TearDown
' Add tests to suite's tests collection
this.addTest("IsValid() true", TestCase__Misc_IsValid_True)
this.addTest("IsValid() false", TestCase__Misc_IsValid_False)
this.addTest("RoundNumber() Floor", TestCase__Misc_RoundNumber_Floor)
this.addTest("RoundNumber() Ceiling", TestCase__Misc_RoundNumber_Ceiling)
return this
end function
'----------------------------------------------------------------
' This function called immediately before running tests of current suite.
'----------------------------------------------------------------
sub MiscTestSuite__SetUp()
end sub
'----------------------------------------------------------------
' This function called immediately after running tests of current suite.
'----------------------------------------------------------------
sub MiscTestSuite__TearDown()
end sub
'----------------------------------------------------------------
' Check if isValid() properly identifies valid items
'
' @return An empty string if test is success or error message if not.
'----------------------------------------------------------------
function TestCase__Misc_IsValid_True() as string
returnResults = ""
testData = [1, 2, [3, 4], { "key": invalid }, [1, 2, 3], CreateObject("roAppInfo")]
for each testItem in testData
returnResults = returnResults + m.AssertTrue(isValid(testItem))
end for
return m.AssertEmpty(returnResults)
end function
'----------------------------------------------------------------
' Check if isValid() properly identifies invalid items
'
' @return An empty string if test is success or error message if not.
'----------------------------------------------------------------
function TestCase__Misc_IsValid_False() as string
returnResults = ""
testData = [invalid, CreateObject("nothing")]
for each testItem in testData
returnResults = m.AssertFalse(isValid(testItem))
end for
return m.AssertEmpty(returnResults)
end function
'----------------------------------------------------------------
' Check if roundNumber() properly rounds down
'
' @return An empty string if test is success or error message if not.
'----------------------------------------------------------------
function TestCase__Misc_RoundNumber_Floor() as string
return m.AssertEqual(roundNumber(9.4), 9)
end function
'----------------------------------------------------------------
' Check if roundNumber() properly rounds up
'
' @return An empty string if test is success or error message if not.
'----------------------------------------------------------------
function TestCase__Misc_RoundNumber_Ceiling() as string
return m.AssertEqual(roundNumber(9.6), 10)
end function

View File

@ -207,13 +207,13 @@ sub setFieldTextValue(field, value)
end sub
' Returns whether or not passed value is valid
function isValid(input) as boolean
function isValid(input as dynamic) as boolean
return input <> invalid
end function
' Returns whether or not passed value is valid and not empty
' Accepts a string, or any countable type (arrays and lists)
function isValidAndNotEmpty(input) as boolean
function isValidAndNotEmpty(input as dynamic) as boolean
if not isValid(input) then return false
' Use roAssociativeArray instead of list so we get access to the doesExist() method
countableTypes = { "array": 1, "list": 1, "roarray": 1, "roassociativearray": 1, "rolist": 1 }

4
test-app/manifest Normal file
View File

@ -0,0 +1,4 @@
title=Rooibos Unit Testing
major_version=0
minor_version=1
build_version=1

View File

@ -0,0 +1,18 @@
namespace tests
class BaseTestSuite extends rooibos.BaseTestSuite
private appController
protected override function setup()
'Do something here all your files need like setup the logger, etc
end function
protected override function beforeEach()
'do things here that all your tests need
end function
protected override function afterEach()
'tidy things up
end function
end class
end namespace

4
test-app/source/Main.bs Normal file
View File

@ -0,0 +1,4 @@
function Main(args)
? "here is my code"
? "hello"
end function

View File

@ -0,0 +1,188 @@
namespace tests
@suite("isValid functions")
class isValidTests extends tests.BaseTestSuite
protected override function setup()
super.setup()
m.myArray = CreateObject("roArray", 3, true)
m.myAssArray = { one: invalid, two: "invalid", three: 123.456 }
m.myEmptyArray = CreateObject("roArray", 0, false)
m.myEmptyList = CreateObject("roList")
m.myList = CreateObject("roList")
m.myList.AddTail("string")
end function
'+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
@describe("isValid()")
'+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
@it("works with booleans")
@params(true, true)
@params(false, true)
function _(value, expectedassertResult)
m.assertEqual(isValid(value), expectedassertResult)
end function
@it("works with integers")
@params(-1234567890, true)
@params(0, true)
@params(1234567890, true)
@params(1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890, true)
function _(value, expectedassertResult)
m.assertEqual(isValid(value), expectedassertResult)
end function
@it("works with floats")
@params(-12.3456789, true)
@params(12.3456789, true)
@params(1.23456E+30, true)
@params(12.3456789!, true)
@params(123456789012345678901234567890123456789012345678901234567890.123456789012345678901234567890123456789012345678901234567890, true)
function _(value, expectedassertResult)
m.assertEqual(isValid(value), expectedassertResult)
end function
@it("works with strings")
@params("", true)
@params(" ", true)
@params("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Augue neque gravida in fermentum et. Eget lorem dolor sed viverra ipsum nunc. At quis risus sed vulputate odio ut enim. Ultricies integer quis auctor elit sed. Egestas congue quisque egestas diam in. Aliquam sem fringilla ut morbi tincidunt. Malesuada bibendum arcu vitae elementum curabitur. Aliquet sagittis id consectetur purus ut faucibus pulvinar. Eget gravida cum sociis natoque. Sollicitudin aliquam ultrices sagittis orci. Ut etiam sit amet nisl purus. Luctus venenatis lectus magna fringilla urna porttitor rhoncus dolor purus. Vitae ultricies leo integer malesuada nunc. Vitae ultricies leo integer malesuada nunc vel risus commodo. Luctus accumsan tortor posuere ac ut. Urna cursus eget nunc scelerisque viverra mauris in. Accumsan sit amet nulla facilisi morbi tempus iaculis urna id. Mauris vitae ultricies leo integer malesuada nunc vel risus commodo. Morbi tincidunt augue interdum velit euismod in pellentesque.", true)
@params("~!@#$%^&*()_-+=`\|]}';:.,/?", true)
@params("true", true)
@params("false", true)
@params("invalid", true)
function _(value, expectedassertResult)
m.assertEqual(isValid(value), expectedassertResult)
end function
@it("works with arrays")
@params([0, 1, 2, 3, 4, 5], true)
@params(["invalid", "one", "two", "three", "four", "five"], true)
@params([invalid, invalid, invalid], true)
function _(value, expectedassertResult)
m.assertEqual(isValid(value), expectedassertResult)
end function
@it("works with associative arrays")
@params({ myInteger: 1, myString: "one", myInvalid: invalid, myEmptyString: "" }, true)
function _(value, expectedassertResult)
m.assertEqual(isValid(value), expectedassertResult)
end function
@it("works with an array of associative arrays")
@params(
[
{
Title: "The Notebook",
releaseDate: "2000"
},
{
Title: "Caddyshack",
releaseDate: "1976"
}
], true)
function _(value, expectedassertResult)
m.assertEqual(isValid(value), expectedassertResult)
end function
@it("works when accessing arrays")
function _()
m.assertEqual(isValid(m.myAssArray.one), false)
m.assertEqual(isValid(m.myAssArray.two), true)
end function
@it("works when accessing an invalid array index")
function _()
m.assertEqual(isValid(m.myAssArray.zero), false)
end function
@it("works with invalid")
@params(invalid, false)
function _(value, expectedassertResult)
m.assertEqual(isValid(value), expectedassertResult)
end function
@it("works with nodes")
@params("#RBSNode", true)
@params("#RBSNode|Group", true)
@params("#RBSNode|Label", true)
@params("#RBSNode|ScrollingLabel", true)
@params("#RBSNode|Poster", true)
@params("#RBSNode|Rectangle", true)
@params("#RBSNode|Font", true)
@params("#RBSNode|Button", true)
@params("#RBSNode|Rectangle", true)
@params("#RBSNode|Overhang", true)
@params("#RBSNode|Audio", true)
@params("#RBSNode|Video", true)
function _(value, expectedassertResult)
m.assertEqual(isValid(value), expectedassertResult)
end function
@it("works with objects")
function _()
myList = CreateObject("roList")
myLongInteger = CreateObject("roLongInteger")
myDouble = CreateObject("roDouble")
myFloat = CreateObject("roFloat")
myInvalid = CreateObject("roInvalid")
m.assertEqual(isValid(myList), true)
m.assertEqual(isValid(myLongInteger), true)
m.assertEqual(isValid(myDouble), true)
m.assertEqual(isValid(myFloat), true)
m.assertEqual(isValid(myInvalid), false)
end function
@it("works with functions")
function _()
myfunc = function(a, b)
return a + b
end function
m.assertEqual(isValid(myfunc(0, 1)), true)
end function
'+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
@describe("isValidAndNotEmpty()")
'+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
@it("works with invalid")
@params(invalid, false)
function _(value, expectedassertResult)
m.assertEqual(isValidAndNotEmpty(value), expectedassertResult)
end function
@it("works with strings")
@params("", false)
@params(" ", false)
@params("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Augue neque gravida in fermentum et. Eget lorem dolor sed viverra ipsum nunc. At quis risus sed vulputate odio ut enim. Ultricies integer quis auctor elit sed. Egestas congue quisque egestas diam in. Aliquam sem fringilla ut morbi tincidunt. Malesuada bibendum arcu vitae elementum curabitur. Aliquet sagittis id consectetur purus ut faucibus pulvinar. Eget gravida cum sociis natoque. Sollicitudin aliquam ultrices sagittis orci. Ut etiam sit amet nisl purus. Luctus venenatis lectus magna fringilla urna porttitor rhoncus dolor purus. Vitae ultricies leo integer malesuada nunc. Vitae ultricies leo integer malesuada nunc vel risus commodo. Luctus accumsan tortor posuere ac ut. Urna cursus eget nunc scelerisque viverra mauris in. Accumsan sit amet nulla facilisi morbi tempus iaculis urna id. Mauris vitae ultricies leo integer malesuada nunc vel risus commodo. Morbi tincidunt augue interdum velit euismod in pellentesque.", true)
@params("~!@#$%^&*()_-+=`\|]}';:.,/?", true)
@params("true", true)
@params("false", true)
@params("invalid", true)
function _(value, expectedassertResult)
m.assertEqual(isValidAndNotEmpty(value), expectedassertResult)
end function
@it("works with arrays")
function _()
m.assertEqual(isValidAndNotEmpty(m.myEmptyArray), false)
m.assertEqual(isValidAndNotEmpty(m.myArray), false)
m.myArray.Push("string")
m.assertEqual(isValidAndNotEmpty(m.myArray), true)
m.myArray.Clear()
m.assertEqual(isValidAndNotEmpty(m.myArray), false)
end function
@it("works with associative arrays")
function _()
m.assertEqual(isValidAndNotEmpty(m.myEmptyArray), false)
m.assertEqual(isValidAndNotEmpty(m.myAssArray), true)
end function
@it("works with lists")
function _()
m.assertEqual(isValidAndNotEmpty(m.myEmptyList), false)
m.assertEqual(isValidAndNotEmpty(m.myList), true)
end function
end class
end namespace