Merge branch 'unstable' into release-sync-166
This commit is contained in:
commit
1eaa3649e3
2
.github/workflows/auto-close-stale-pr.yml
vendored
2
.github/workflows/auto-close-stale-pr.yml
vendored
|
@ -18,4 +18,4 @@ jobs:
|
||||||
days-before-pr-stale: 21
|
days-before-pr-stale: 21
|
||||||
days-before-pr-close: 7
|
days-before-pr-close: 7
|
||||||
exempt-draft-pr: true
|
exempt-draft-pr: true
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.JF_BOT_TOKEN }}
|
||||||
|
|
3
.github/workflows/automations.yml
vendored
3
.github/workflows/automations.yml
vendored
|
@ -30,4 +30,5 @@ jobs:
|
||||||
uses: eps1lon/actions-label-merge-conflict@releases/2.x
|
uses: eps1lon/actions-label-merge-conflict@releases/2.x
|
||||||
with:
|
with:
|
||||||
dirtyLabel: "merge conflict"
|
dirtyLabel: "merge conflict"
|
||||||
repoToken: ${{ secrets.GITHUB_TOKEN }}
|
commentOnDirty: "This pull request has merge conflicts. Please resolve the conflicts so the PR can be reviewed. Thanks!"
|
||||||
|
repoToken: ${{ secrets.JF_BOT_TOKEN }}
|
||||||
|
|
2
.github/workflows/build-dev.yml
vendored
2
.github/workflows/build-dev.yml
vendored
|
@ -12,7 +12,7 @@ jobs:
|
||||||
dev:
|
dev:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3
|
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3
|
||||||
- uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3
|
- uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3
|
||||||
with:
|
with:
|
||||||
node-version: "lts/*"
|
node-version: "lts/*"
|
||||||
|
|
6
.github/workflows/build-prod.yml
vendored
6
.github/workflows/build-prod.yml
vendored
|
@ -13,7 +13,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout master (the latest release)
|
- name: Checkout master (the latest release)
|
||||||
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3
|
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3
|
||||||
with:
|
with:
|
||||||
ref: master
|
ref: master
|
||||||
- name: Install jq to parse json
|
- name: Install jq to parse json
|
||||||
|
@ -33,7 +33,7 @@ jobs:
|
||||||
- name: Save old Makefile version
|
- name: Save old Makefile version
|
||||||
run: awk 'BEGIN { FS=" = " } /^VERSION/ { print "oldMakeVersion="$2; }' Makefile >> $GITHUB_ENV
|
run: awk 'BEGIN { FS=" = " } /^VERSION/ { print "oldMakeVersion="$2; }' Makefile >> $GITHUB_ENV
|
||||||
- name: Checkout PR branch
|
- name: Checkout PR branch
|
||||||
uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3
|
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3
|
||||||
- name: Save new package.json version
|
- name: Save new package.json version
|
||||||
run: echo "newPackVersion=$(jq -r ".version" package.json)" >> $GITHUB_ENV
|
run: echo "newPackVersion=$(jq -r ".version" package.json)" >> $GITHUB_ENV
|
||||||
- name: package.json version must be updated
|
- name: package.json version must be updated
|
||||||
|
@ -61,7 +61,7 @@ jobs:
|
||||||
prod:
|
prod:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3
|
- uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3
|
||||||
- uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3
|
- uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3
|
||||||
with:
|
with:
|
||||||
node-version: "lts/*"
|
node-version: "lts/*"
|
||||||
|
|
17
.gitignore
vendored
17
.gitignore
vendored
|
@ -1,18 +1,17 @@
|
||||||
*.svg
|
*.svg
|
||||||
jellyfin-roku.zip
|
|
||||||
source/globals.brs
|
|
||||||
.env
|
.env
|
||||||
|
# BrightScript
|
||||||
###BrightScript specific
|
|
||||||
dist/apps
|
dist/apps
|
||||||
out/
|
out/
|
||||||
|
build/
|
||||||
roku_modules
|
roku_modules
|
||||||
|
source/globals.brs
|
||||||
#NPM modules
|
jellyfin-roku.zip
|
||||||
|
# Rooibos
|
||||||
|
bsconfig-tdd.json
|
||||||
|
# NPM
|
||||||
node_modules/
|
node_modules/
|
||||||
|
# Eclipse
|
||||||
#Eclipse
|
|
||||||
.buildpath
|
.buildpath
|
||||||
.project
|
.project
|
||||||
.settings
|
.settings
|
418
.vscode/brighterscript.code-snippets
vendored
Normal file
418
.vscode/brighterscript.code-snippets
vendored
Normal 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
62
.vscode/launch.json
vendored
|
@ -4,7 +4,7 @@
|
||||||
{
|
{
|
||||||
"type": "brightscript",
|
"type": "brightscript",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"name": "Jellyfin Debug: Launch",
|
"name": "Jellyfin Debug",
|
||||||
"stopOnEntry": false,
|
"stopOnEntry": false,
|
||||||
// To enable RALE:
|
// To enable RALE:
|
||||||
// set "brightscript.debug.raleTrackerTaskFileLocation": "/absolute/path/to/rale/TrackerTask.xml" in your vscode user settings
|
// set "brightscript.debug.raleTrackerTaskFileLocation": "/absolute/path/to/rale/TrackerTask.xml" in your vscode user settings
|
||||||
|
@ -22,6 +22,66 @@
|
||||||
"source/**/*",
|
"source/**/*",
|
||||||
"manifest"
|
"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
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
9
.vscode/settings.json
vendored
9
.vscode/settings.json
vendored
|
@ -2,6 +2,13 @@
|
||||||
"files.associations": {
|
"files.associations": {
|
||||||
"*.ts": "xml"
|
"*.ts": "xml"
|
||||||
},
|
},
|
||||||
|
"[xml]": {
|
||||||
|
"editor.defaultFormatter": "redhat.vscode-xml"
|
||||||
|
},
|
||||||
|
"[markdown]": {
|
||||||
|
"editor.defaultFormatter": "DavidAnson.vscode-markdownlint"
|
||||||
|
},
|
||||||
"xml.format.maxLineWidth": 0,
|
"xml.format.maxLineWidth": 0,
|
||||||
"editor.formatOnSave": true
|
"editor.formatOnSave": true,
|
||||||
|
"brightscript.bsdk": "node_modules/brighterscript"
|
||||||
}
|
}
|
41
.vscode/tasks.json
vendored
Normal file
41
.vscode/tasks.json
vendored
Normal 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
44
bsconfig-tests.json
Normal 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
|
||||||
|
}
|
|
@ -9,11 +9,11 @@
|
||||||
"settings/*.*"
|
"settings/*.*"
|
||||||
],
|
],
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"@rokucommunity/bslint"
|
"@rokucommunity/bslint",
|
||||||
|
"rooibos-roku"
|
||||||
],
|
],
|
||||||
"diagnosticFilters": [
|
"diagnosticFilters": [
|
||||||
"**/roku_modules/**/*",
|
"node_modules/**",
|
||||||
"**/testFramework/*",
|
"**/roku_modules/**"
|
||||||
"**/tests/*"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -1,6 +1,9 @@
|
||||||
{
|
{
|
||||||
"files": [
|
"files": [
|
||||||
"source/**/*.brs",
|
"source/**/*.brs",
|
||||||
"components/**/*.brs"
|
"source/**/*.bs",
|
||||||
|
"components/**/*.brs",
|
||||||
|
"components/**/*.bs",
|
||||||
|
"test-app/**/*.bs"
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -13,7 +13,7 @@ sub loadItems()
|
||||||
m.top.content = [LoadItems_VideoPlayer(m.top.itemId)]
|
m.top.content = [LoadItems_VideoPlayer(m.top.itemId)]
|
||||||
end sub
|
end sub
|
||||||
|
|
||||||
function LoadItems_VideoPlayer(id, mediaSourceId = invalid, audio_stream_idx = 1, subtitle_idx = -1, forceTranscoding = false, showIntro = true, allowResumeDialog = true)
|
function LoadItems_VideoPlayer(id as string, mediaSourceId = invalid as dynamic, audio_stream_idx = 1 as integer, subtitle_idx = -1 as integer, forceTranscoding = false as boolean, showIntro = true as boolean, allowResumeDialog = true as boolean) as dynamic
|
||||||
|
|
||||||
video = {}
|
video = {}
|
||||||
video.id = id
|
video.id = id
|
||||||
|
@ -32,7 +32,7 @@ function LoadItems_VideoPlayer(id, mediaSourceId = invalid, audio_stream_idx = 1
|
||||||
return video
|
return video
|
||||||
end function
|
end function
|
||||||
|
|
||||||
sub LoadItems_AddVideoContent(video, mediaSourceId, audio_stream_idx = 1, subtitle_idx = -1, playbackPosition = -1, forceTranscoding = false, showIntro = true, allowResumeDialog = true)
|
sub LoadItems_AddVideoContent(video as object, mediaSourceId as dynamic, audio_stream_idx = 1 as integer, subtitle_idx = -1 as integer, playbackPosition = -1 as integer, forceTranscoding = false as boolean, showIntro = true as boolean, allowResumeDialog = true as boolean)
|
||||||
|
|
||||||
meta = ItemMetaData(video.id)
|
meta = ItemMetaData(video.id)
|
||||||
|
|
||||||
|
|
4
components/PlayedCheckmark.brs
Normal file
4
components/PlayedCheckmark.brs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
sub init()
|
||||||
|
checkmark = m.top.findNode("checkmark")
|
||||||
|
checkmark.font.size = 48
|
||||||
|
end sub
|
7
components/PlayedCheckmark.xml
Normal file
7
components/PlayedCheckmark.xml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<component name="PlayedCheckmark" extends="Rectangle">
|
||||||
|
<children>
|
||||||
|
<Label id="checkmark" width="60" height="50" font="font:MediumBoldSystemFont" horizAlign="center" vertAlign="bottom" text="✓" />
|
||||||
|
</children>
|
||||||
|
<script type="text/brightscript" uri="PlayedCheckmark.brs" />
|
||||||
|
</component>
|
|
@ -28,8 +28,9 @@ sub setData()
|
||||||
end if
|
end if
|
||||||
|
|
||||||
else if datum.type = "Episode"
|
else if datum.type = "Episode"
|
||||||
imgParams = { "AddPlayedIndicator": datum.UserData.Played }
|
m.top.isWatched = datum.UserData.Played
|
||||||
|
|
||||||
|
imgParams = {}
|
||||||
imgParams.Append({ "maxHeight": 261 })
|
imgParams.Append({ "maxHeight": 261 })
|
||||||
imgParams.Append({ "maxWidth": 464 })
|
imgParams.Append({ "maxWidth": 464 })
|
||||||
|
|
||||||
|
@ -68,8 +69,9 @@ sub setData()
|
||||||
end if
|
end if
|
||||||
|
|
||||||
else if datum.type = "Movie"
|
else if datum.type = "Movie"
|
||||||
imgParams = { AddPlayedIndicator: datum.UserData.Played }
|
m.top.isWatched = datum.UserData.Played
|
||||||
|
|
||||||
|
imgParams = {}
|
||||||
imgParams.Append({ "maxHeight": 261 })
|
imgParams.Append({ "maxHeight": 261 })
|
||||||
imgParams.Append({ "maxWidth": 175 })
|
imgParams.Append({ "maxWidth": 175 })
|
||||||
|
|
||||||
|
@ -92,8 +94,9 @@ sub setData()
|
||||||
end if
|
end if
|
||||||
|
|
||||||
else if datum.type = "Video"
|
else if datum.type = "Video"
|
||||||
imgParams = { AddPlayedIndicator: datum.UserData.Played }
|
m.top.isWatched = datum.UserData.Played
|
||||||
|
|
||||||
|
imgParams = {}
|
||||||
imgParams.Append({ "maxHeight": 261 })
|
imgParams.Append({ "maxHeight": 261 })
|
||||||
imgParams.Append({ "maxWidth": 175 })
|
imgParams.Append({ "maxWidth": 175 })
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<?xml version="1.0" encoding="utf-8" ?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<component name="HomeData" extends="ContentNode">
|
<component name="HomeData" extends="ContentNode">
|
||||||
<interface>
|
<interface>
|
||||||
<field id="id" type="string" />
|
<field id="id" type="string" />
|
||||||
|
@ -13,6 +13,7 @@
|
||||||
<field id="imageWidth" type="integer" value="464" />
|
<field id="imageWidth" type="integer" value="464" />
|
||||||
<field id="PlayedPercentage" type="float" value="0" />
|
<field id="PlayedPercentage" type="float" value="0" />
|
||||||
<field id="usePoster" type="bool" value="false" />
|
<field id="usePoster" type="bool" value="false" />
|
||||||
|
<field id="isWatched" type="bool" value="false" />
|
||||||
</interface>
|
</interface>
|
||||||
<script type="text/brightscript" uri="pkg:/source/api/baserequest.brs" />
|
<script type="text/brightscript" uri="pkg:/source/api/baserequest.brs" />
|
||||||
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
|
<script type="text/brightscript" uri="pkg:/source/utils/config.brs" />
|
||||||
|
|
|
@ -9,6 +9,7 @@ sub init()
|
||||||
m.itemPoster.observeField("loadStatus", "onPosterLoadStatusChanged")
|
m.itemPoster.observeField("loadStatus", "onPosterLoadStatusChanged")
|
||||||
m.unplayedCount = m.top.findNode("unplayedCount")
|
m.unplayedCount = m.top.findNode("unplayedCount")
|
||||||
m.unplayedEpisodeCount = m.top.findNode("unplayedEpisodeCount")
|
m.unplayedEpisodeCount = m.top.findNode("unplayedEpisodeCount")
|
||||||
|
m.playedIndicator = m.top.findNode("playedIndicator")
|
||||||
|
|
||||||
m.showProgressBarAnimation = m.top.findNode("showProgressBar")
|
m.showProgressBarAnimation = m.top.findNode("showProgressBar")
|
||||||
m.showProgressBarField = m.top.findNode("showProgressBarField")
|
m.showProgressBarField = m.top.findNode("showProgressBarField")
|
||||||
|
@ -37,6 +38,12 @@ sub itemContentChanged()
|
||||||
m.itemIcon.uri = itemData.iconUrl
|
m.itemIcon.uri = itemData.iconUrl
|
||||||
end if
|
end if
|
||||||
|
|
||||||
|
if itemData.isWatched
|
||||||
|
m.playedIndicator.visible = true
|
||||||
|
m.unplayedCount.visible = false
|
||||||
|
else
|
||||||
|
m.playedIndicator.visible = false
|
||||||
|
|
||||||
if LCase(itemData.type) = "series"
|
if LCase(itemData.type) = "series"
|
||||||
if get_user_setting("ui.tvshows.disableUnwatchedEpisodeCount", "false") = "false"
|
if get_user_setting("ui.tvshows.disableUnwatchedEpisodeCount", "false") = "false"
|
||||||
if isValid(itemData.json.UserData) and isValid(itemData.json.UserData.UnplayedItemCount)
|
if isValid(itemData.json.UserData) and isValid(itemData.json.UserData.UnplayedItemCount)
|
||||||
|
@ -47,6 +54,7 @@ sub itemContentChanged()
|
||||||
end if
|
end if
|
||||||
end if
|
end if
|
||||||
end if
|
end if
|
||||||
|
end if
|
||||||
|
|
||||||
' Format the Data based on the type of Home Data
|
' Format the Data based on the type of Home Data
|
||||||
if itemData.type = "CollectionFolder" or itemData.type = "UserView" or itemData.type = "Channel"
|
if itemData.type = "CollectionFolder" or itemData.type = "UserView" or itemData.type = "Channel"
|
||||||
|
@ -64,6 +72,8 @@ sub itemContentChanged()
|
||||||
return
|
return
|
||||||
end if
|
end if
|
||||||
|
|
||||||
|
playedIndicatorLeftPosition = m.itemPoster.width - 60
|
||||||
|
m.playedIndicator.translation = [playedIndicatorLeftPosition, 0]
|
||||||
|
|
||||||
m.itemText.height = 34
|
m.itemText.height = 34
|
||||||
m.itemText.font.size = 25
|
m.itemText.font.size = 25
|
||||||
|
@ -166,6 +176,20 @@ sub itemContentChanged()
|
||||||
return
|
return
|
||||||
end if
|
end if
|
||||||
|
|
||||||
|
if itemData.type = "BoxSet"
|
||||||
|
m.itemText.text = itemData.name
|
||||||
|
m.itemPoster.uri = itemData.posterURL
|
||||||
|
|
||||||
|
' Set small text to number of items in the collection
|
||||||
|
if isValid(itemData.json) and isValid(itemData.json.ChildCount)
|
||||||
|
m.itemTextExtra.text = StrI(itemData.json.ChildCount).trim() + " item"
|
||||||
|
if itemData.json.ChildCount > 1
|
||||||
|
m.itemTextExtra.text += "s"
|
||||||
|
end if
|
||||||
|
end if
|
||||||
|
return
|
||||||
|
end if
|
||||||
|
|
||||||
if itemData.type = "Series"
|
if itemData.type = "Series"
|
||||||
|
|
||||||
m.itemText.text = itemData.name
|
m.itemText.text = itemData.name
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
<Rectangle id="unplayedCount" visible="false" width="90" height="60" color="#00a4dcFF" translation="[375, 0]">
|
<Rectangle id="unplayedCount" visible="false" width="90" height="60" color="#00a4dcFF" translation="[375, 0]">
|
||||||
<Label id="unplayedEpisodeCount" width="90" height="60" font="font:SmallestBoldSystemFont" horizAlign="center" vertAlign="center" />
|
<Label id="unplayedEpisodeCount" width="90" height="60" font="font:SmallestBoldSystemFont" horizAlign="center" vertAlign="center" />
|
||||||
</Rectangle>
|
</Rectangle>
|
||||||
|
<PlayedCheckmark id="playedIndicator" color="#00a4dcFF" width="60" height="46" visible="false" />
|
||||||
</Poster>
|
</Poster>
|
||||||
<Rectangle id="progressBackground" visible="false" color="0x00000098" width="464" height="8" translation="[8,260]">
|
<Rectangle id="progressBackground" visible="false" color="0x00000098" width="464" height="8" translation="[8,260]">
|
||||||
<Rectangle id="progress" color="#00a4dcFF" width="0" height="8" />
|
<Rectangle id="progress" color="#00a4dcFF" width="0" height="8" />
|
||||||
|
|
|
@ -132,6 +132,12 @@ sub loadItems()
|
||||||
' Skip Books for now as we don't support it (issue #558)
|
' Skip Books for now as we don't support it (issue #558)
|
||||||
if item.Type <> "Book"
|
if item.Type <> "Book"
|
||||||
tmp = CreateObject("roSGNode", "HomeData")
|
tmp = CreateObject("roSGNode", "HomeData")
|
||||||
|
|
||||||
|
params = {}
|
||||||
|
params["Tags"] = item.PrimaryImageTag
|
||||||
|
params["MaxWidth"] = 234
|
||||||
|
params["MaxHeight"] = 330
|
||||||
|
tmp.posterURL = ImageUrl(item.Id, "Primary", params)
|
||||||
tmp.json = item
|
tmp.json = item
|
||||||
results.push(tmp)
|
results.push(tmp)
|
||||||
end if
|
end if
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!DOCTYPE TS>
|
<!DOCTYPE TS>
|
||||||
<TS version="2.0" language="es_ES" sourcelanguage="en_US">
|
<TS version="2.0" language="es_ES" sourcelanguage="en_US">
|
||||||
<defaultcodec>UTF-8</defaultcodec>
|
<defaultcodec>UTF-8</defaultcodec>
|
||||||
<context>
|
<context>
|
||||||
<name>default</name>
|
<name>default</name>
|
||||||
<message>
|
<message>
|
||||||
<source>192.168.1.100:8096 or https://example.com/jellyfin</source>
|
<source>192.168.1.100:8096 or https://example.com/jellyfin</source>
|
||||||
|
@ -240,8 +240,8 @@
|
||||||
<source>Server</source>
|
<source>Server</source>
|
||||||
<translation>Servidor</translation>
|
<translation>Servidor</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name></name>
|
<name></name>
|
||||||
<message>
|
<message>
|
||||||
<source>Sign Out</source>
|
<source>Sign Out</source>
|
||||||
|
@ -3470,5 +3470,319 @@
|
||||||
<source>Disabled</source>
|
<source>Disabled</source>
|
||||||
<translation>Deshabilitado</translation>
|
<translation>Deshabilitado</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
<message>
|
||||||
|
<source>Change Server</source>
|
||||||
|
<translation>Cambiar servidor</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Save Credentials?</source>
|
||||||
|
<translation>¿Guardar credenciales?</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Sign Out</source>
|
||||||
|
<translation>Cerrar sesión</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Born</source>
|
||||||
|
<translation>Nacido/a</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>TV Shows</source>
|
||||||
|
<translation>Programas de televisión</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Error During Playback</source>
|
||||||
|
<translation>Error durante la reproducción</translation>
|
||||||
|
<extracomment>Dialog title when error occurs during playback</extracomment>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Unable to load Channel Data from the server</source>
|
||||||
|
<translation>No es posible cargar los datos del canal desde el servidor</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<comment>Name or Title field of media item</comment>
|
||||||
|
<source>TITLE</source>
|
||||||
|
<translation>Nombre</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Additional Parts</source>
|
||||||
|
<translation>Partes adicionales</translation>
|
||||||
|
<extracomment>Additional parts of a video</extracomment>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Friday</source>
|
||||||
|
<translation>Viernes</translation>
|
||||||
|
<extracomment>Day of Week</extracomment>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Close</source>
|
||||||
|
<translation>Cerrar</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>On Now</source>
|
||||||
|
<translation>Transmitiendo Ahora</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Error Retrieving Content</source>
|
||||||
|
<translation>Error al recuperar el contenido</translation>
|
||||||
|
<extracomment>Dialog title when unable to load Content from Server</extracomment>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>There was an error retrieving the data for this item from the server.</source>
|
||||||
|
<translation>Ha habido un error al intentar obtener este ítem del servidor.</translation>
|
||||||
|
<extracomment>Dialog detail when unable to load Content from Server</extracomment>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>An error was encountered while playing this item.</source>
|
||||||
|
<translation>Se ha encontrado un error durante la reproduccion de este ítem.</translation>
|
||||||
|
<extracomment>Dialog detail when error occurs during playback</extracomment>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<comment>Message displayed in Item Grid when no item to display. %1 is container type (e.g. Boxset, Collection, Folder, etc)</comment>
|
||||||
|
<source>NO_ITEMS</source>
|
||||||
|
<translation>Este %1 no contiene ítems</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>DATE_PLAYED</source>
|
||||||
|
<translation>Fecha de reproducción</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>OFFICIAL_RATING</source>
|
||||||
|
<translation>Valoración parental</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<comment>Title of Tab for options to filter library content</comment>
|
||||||
|
<source>TAB_FILTER</source>
|
||||||
|
<translation>Filtrar</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Died</source>
|
||||||
|
<translation>Fallecido/a</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Press 'OK' to Close</source>
|
||||||
|
<translation>Presiona 'Aceptar' para cerrar</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Special Features</source>
|
||||||
|
<translation>Características especiales</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>yesterday</source>
|
||||||
|
<translation>ayer</translation>
|
||||||
|
<extracomment>Previous day</extracomment>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>tomorrow</source>
|
||||||
|
<translation>mañana</translation>
|
||||||
|
<extracomment>Next day</extracomment>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Sunday</source>
|
||||||
|
<translation>Domingo</translation>
|
||||||
|
<extracomment>Day of Week</extracomment>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Monday</source>
|
||||||
|
<translation>Lunes</translation>
|
||||||
|
<extracomment>Day of Week</extracomment>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Tuesday</source>
|
||||||
|
<translation>Martes</translation>
|
||||||
|
<extracomment>Day of Week</extracomment>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Wednesday</source>
|
||||||
|
<translation>Miércoles</translation>
|
||||||
|
<extracomment>Day of Week</extracomment>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Thursday</source>
|
||||||
|
<translation>Jueves</translation>
|
||||||
|
<extracomment>Day of Week</extracomment>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Saturday</source>
|
||||||
|
<translation>Sábado</translation>
|
||||||
|
<extracomment>Day of Week</extracomment>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Started at</source>
|
||||||
|
<translation>Empezado a las</translation>
|
||||||
|
<extracomment>(Past Tense) For defining time when a program started today (e.g. Started at 08:00) </extracomment>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Starts</source>
|
||||||
|
<translation>Comenzará</translation>
|
||||||
|
<extracomment>(Future Tense) For defining a day and time when a program will start (e.g. Starts Wednesday, 08:00) </extracomment>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Ended at</source>
|
||||||
|
<translation>Terminado a las</translation>
|
||||||
|
<extracomment>(Past Tense) For defining time when a program will ended (e.g. Ended at 08:00) </extracomment>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Live</source>
|
||||||
|
<translation>Directo</translation>
|
||||||
|
<extracomment>If TV Show is being broadcast live (not pre-recorded)</extracomment>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Channels</source>
|
||||||
|
<translation>Canales</translation>
|
||||||
|
<extracomment>Menu option for showing Live TV Channel List</extracomment>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>TV Guide</source>
|
||||||
|
<translation>Guía de Televisión</translation>
|
||||||
|
<extracomment>Menu option for showing Live TV Guide / Schedule</extracomment>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Cancel Recording</source>
|
||||||
|
<translation>Cancelar la Grabación</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Connecting to Server</source>
|
||||||
|
<translation>Conectando al Servidor</translation>
|
||||||
|
<extracomment>Message to display to user while client is attempting to connect to the server</extracomment>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Not found</source>
|
||||||
|
<translation>No encontrado</translation>
|
||||||
|
<extracomment>Title of message box when the requested content is not found on the server</extracomment>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>The requested content does not exist on the server</source>
|
||||||
|
<translation>El contenido solicitado no existe en el servidor</translation>
|
||||||
|
<extracomment>Content of message box when the requested content is not found on the server</extracomment>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Pick a Jellyfin server from the local network</source>
|
||||||
|
<translation>Seleccione un servidor Jellyfin disponible en su red local</translation>
|
||||||
|
<extracomment>Instructions on initial app launch when the user is asked to pick a server from a list</extracomment>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>RUNTIME</source>
|
||||||
|
<translation>Duración</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<comment>Title of Tab for switching "views" when looking at a library</comment>
|
||||||
|
<source>TAB_VIEW</source>
|
||||||
|
<translation>Vista</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Unknown</source>
|
||||||
|
<translation>Desconocido</translation>
|
||||||
|
<extracomment>Title for a cast member for which we have no information for</extracomment>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>IMDB_RATING</source>
|
||||||
|
<translation>Valoración IMDb</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>DATE_ADDED</source>
|
||||||
|
<translation>Fecha añadido</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Age</source>
|
||||||
|
<translation>Edad</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Cast & Crew</source>
|
||||||
|
<translation>Elenco y equipo</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>More Like This</source>
|
||||||
|
<translation>Más de este estilo</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>today</source>
|
||||||
|
<translation>hoy</translation>
|
||||||
|
<extracomment>Current day</extracomment>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Repeat</source>
|
||||||
|
<translation>Repetir</translation>
|
||||||
|
<extracomment>If TV Shows has previously been broadcasted</extracomment>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Movies (Presentation)</source>
|
||||||
|
<translation>Películas (en modo presentación)</translation>
|
||||||
|
<extracomment>Movie library view option</extracomment>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Movies (Grid)</source>
|
||||||
|
<translation>Películas (cuadrícula)</translation>
|
||||||
|
<extracomment>Movie library view option</extracomment>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Starts at</source>
|
||||||
|
<translation>Comienza a las</translation>
|
||||||
|
<extracomment>(Future Tense) For defining time when a program will start today (e.g. Starts at 08:00) </extracomment>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Record Series</source>
|
||||||
|
<translation>Grabar Serie</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Cancel Series Recording</source>
|
||||||
|
<translation>Cancelar la Grabación de la Serie</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Ends at</source>
|
||||||
|
<translation>Termina a las</translation>
|
||||||
|
<extracomment>(Past Tense) For defining a day and time when a program ended (e.g. Ended Wednesday, 08:00) </extracomment>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>View Channel</source>
|
||||||
|
<translation>Ver Canal</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Record</source>
|
||||||
|
<translation>Grabar</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Started</source>
|
||||||
|
<translation>Empezó</translation>
|
||||||
|
<extracomment>(Past Tense) For defining a day and time when a program started (e.g. Started Wednesday, 08:00) </extracomment>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>CRITIC_RATING</source>
|
||||||
|
<translation>Puntuación de la crítica</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Enter the server name or IP address</source>
|
||||||
|
<translation>Introduce el nombre del servidor o la dirección IP</translation>
|
||||||
|
<extracomment>Title of KeyboardDialog when manually entering a server URL</extracomment>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Delete Saved</source>
|
||||||
|
<translation>Eliminar Guardado</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Loading Channel Data</source>
|
||||||
|
<translation>Cargando información del canal</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Error loading Channel Data</source>
|
||||||
|
<translation>Error al cargar la información del canal</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>PLAY_COUNT</source>
|
||||||
|
<translation>Número de reproducciones</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>RELEASE_DATE</source>
|
||||||
|
<translation>Fecha de estreno</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<comment>Title of Tab for options to sort library content</comment>
|
||||||
|
<source>TAB_SORT</source>
|
||||||
|
<translation>Ordenar</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Movies</source>
|
||||||
|
<translation>Películas</translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
</TS>
|
</TS>
|
|
@ -1,8 +1,8 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!DOCTYPE TS>
|
<!DOCTYPE TS>
|
||||||
<TS version="2.0" language="fr_CA" sourcelanguage="en_US">
|
<TS version="2.0" language="fr_CA" sourcelanguage="en_US">
|
||||||
<defaultcodec>UTF-8</defaultcodec>
|
<defaultcodec>UTF-8</defaultcodec>
|
||||||
<context>
|
<context>
|
||||||
<name>default</name>
|
<name>default</name>
|
||||||
<message>
|
<message>
|
||||||
<source>192.168.1.100:8096 or https://example.com/jellyfin</source>
|
<source>192.168.1.100:8096 or https://example.com/jellyfin</source>
|
||||||
|
@ -180,8 +180,8 @@
|
||||||
<source>Server</source>
|
<source>Server</source>
|
||||||
<translation>Servidor</translation>
|
<translation>Servidor</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name></name>
|
<name></name>
|
||||||
<message>
|
<message>
|
||||||
<source>Sign Out</source>
|
<source>Sign Out</source>
|
||||||
|
@ -2691,5 +2691,30 @@
|
||||||
<source>Error loading Channel Data</source>
|
<source>Error loading Channel Data</source>
|
||||||
<translation>Erro ao carregar os Dados do Canal</translation>
|
<translation>Erro ao carregar os Dados do Canal</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
<message>
|
||||||
|
<source>Change Server</source>
|
||||||
|
<translation type="unfinished">Alterar Servidor</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Sign Out</source>
|
||||||
|
<translation type="unfinished">Sair</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Save Credentials?</source>
|
||||||
|
<translation type="unfinished">Salvar credenciais?</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Delete Saved</source>
|
||||||
|
<translation type="unfinished">Excluir salvo</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>On Now</source>
|
||||||
|
<translation type="unfinished">Ativo agora</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Error Retrieving Content</source>
|
||||||
|
<translation type="unfinished">Erro ao recuperar conteúdo</translation>
|
||||||
|
<extracomment>Dialog title when unable to load Content from Server</extracomment>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
</TS>
|
</TS>
|
6080
package-lock.json
generated
6080
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
48
package.json
48
package.json
|
@ -2,29 +2,42 @@
|
||||||
"name": "jellyfin-roku",
|
"name": "jellyfin-roku",
|
||||||
"version": "1.6.6",
|
"version": "1.6.6",
|
||||||
"description": "Roku app for Jellyfin media server",
|
"description": "Roku app for Jellyfin media server",
|
||||||
"main": "index.js",
|
"dependencies": {
|
||||||
|
"api": "npm:jellyfin-api-bs-client@1.0.6",
|
||||||
|
"bgv": "npm:button-group-vert@1.0.2",
|
||||||
|
"brighterscript-formatter": "1.6.26",
|
||||||
|
"intKeyboard": "npm:integer-keyboard@1.0.12",
|
||||||
|
"sob": "npm:slide-out-button@1.0.1"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rokucommunity/bslint": "0.8.2",
|
"@rokucommunity/bslint": "0.8.3",
|
||||||
"brighterscript": "0.64.0",
|
"brighterscript": "0.64.2",
|
||||||
"ropm": "0.10.12",
|
"bslib": "npm:@rokucommunity/bslib@0.1.1",
|
||||||
"jshint": "^2.13.6",
|
"jshint": "2.13.6",
|
||||||
"markdownlint-cli2": "0.6.0",
|
"markdownlint-cli2": "0.7.0",
|
||||||
"spellchecker-cli": "6.1.1"
|
"rimraf": "5.0.0",
|
||||||
|
"roku-deploy": "3.10.1",
|
||||||
|
"roku-log-bsc-plugin": "0.8.1",
|
||||||
|
"rooibos-roku": "5.4.2",
|
||||||
|
"ropm": "0.10.13",
|
||||||
|
"spellchecker-cli": "6.1.1",
|
||||||
|
"undent": "0.1.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"postinstall": "npx ropm copy",
|
"build-tests": "npx rimraf build/ && npx bsc --project bsconfig-tests.json",
|
||||||
"validate": "npx bsc --copy-to-staging=false --create-package=false",
|
"build-tdd": "npx rimraf build/ && npx bsc --project bsconfig-tdd.json",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"check-formatting": "npx bsfmt --check",
|
||||||
|
"format": "npx bsfmt --write",
|
||||||
"lint": "bslint",
|
"lint": "bslint",
|
||||||
"lint-json": "jshint --extra-ext .json --verbose --exclude node_modules ./",
|
"lint-json": "jshint --extra-ext .json --verbose --exclude node_modules ./",
|
||||||
"lint-markdown": "markdownlint-cli2 \"**/*.md\" \"#node_modules\"",
|
"lint-markdown": "markdownlint-cli2 \"**/*.md\" \"#node_modules\"",
|
||||||
"lint-spelling": "spellchecker -d dictionary.txt --files \"**/*.md\" \"**/.*/**/*.md\" \"!node_modules/**/*.md\"",
|
"lint-spelling": "spellchecker -d dictionary.txt --files \"**/*.md\" \"**/.*/**/*.md\" \"!node_modules/**/*.md\"",
|
||||||
"check-formatting": "npx bsfmt --check",
|
"postinstall": "npx ropm copy",
|
||||||
"format": "npx bsfmt --write"
|
"validate": "npx bsc --copy-to-staging=false --create-package=false"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/jellyfin/jellyfin-roku.git"
|
"url": "https://github.com/jellyfin/jellyfin-roku.git"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"jellyfin",
|
"jellyfin",
|
||||||
|
@ -35,12 +48,5 @@
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/jellyfin/jellyfin-roku/issues"
|
"url": "https://github.com/jellyfin/jellyfin-roku/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/jellyfin/jellyfin-roku#readme",
|
"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"
|
|
||||||
}
|
|
||||||
}
|
}
|
169
source/Main.brs
169
source/Main.brs
|
@ -1,26 +1,6 @@
|
||||||
sub Main (args as dynamic) as void
|
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.
|
' The main function that runs when the application is launched.
|
||||||
m.screen = CreateObject("roSGScreen")
|
m.screen = CreateObject("roSGScreen")
|
||||||
|
|
||||||
' Set global constants
|
' Set global constants
|
||||||
setConstants()
|
setConstants()
|
||||||
' Write screen tracker for screensaver
|
' Write screen tracker for screensaver
|
||||||
|
@ -77,6 +57,7 @@ sub Main (args as dynamic) as void
|
||||||
end if
|
end if
|
||||||
|
|
||||||
' Only show the Whats New popup the first time a user runs a new client version.
|
' 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")
|
if appInfo.GetVersion() <> get_setting("LastRunVersion")
|
||||||
' Ensure the user hasn't disabled Whats New popups
|
' Ensure the user hasn't disabled Whats New popups
|
||||||
if get_user_setting("load.allowwhatsnew") = "true"
|
if get_user_setting("load.allowwhatsnew") = "true"
|
||||||
|
@ -158,7 +139,7 @@ sub Main (args as dynamic) as void
|
||||||
|
|
||||||
m.selectedItemType = selectedItem.type
|
m.selectedItemType = selectedItem.type
|
||||||
|
|
||||||
if selectedItem.type = "CollectionFolder"
|
if selectedItem.type = "CollectionFolder" or selectedItem.type = "BoxSet"
|
||||||
if selectedItem.collectionType = "movies"
|
if selectedItem.collectionType = "movies"
|
||||||
group = CreateMovieLibraryView(selectedItem)
|
group = CreateMovieLibraryView(selectedItem)
|
||||||
else if selectedItem.collectionType = "music"
|
else if selectedItem.collectionType = "music"
|
||||||
|
@ -621,149 +602,3 @@ sub Main (args as dynamic) as void
|
||||||
end while
|
end while
|
||||||
|
|
||||||
end sub
|
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
|
|
||||||
|
|
|
@ -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()
|
function CreateServerGroup()
|
||||||
screen = CreateObject("roSGNode", "SetServerScreen")
|
screen = CreateObject("roSGNode", "SetServerScreen")
|
||||||
screen.optionsAvailable = true
|
screen.optionsAvailable = true
|
||||||
|
@ -330,36 +476,57 @@ function CreateHomeGroup()
|
||||||
return group
|
return group
|
||||||
end function
|
end function
|
||||||
|
|
||||||
function CreateMovieDetailsGroup(movie)
|
function CreateMovieDetailsGroup(movie as object) as dynamic
|
||||||
|
' validate movie node
|
||||||
|
if not isValid(movie) or not isValid(movie.id) then return invalid
|
||||||
|
|
||||||
startLoadingSpinner()
|
startLoadingSpinner()
|
||||||
|
' get movie meta data
|
||||||
|
movieMetaData = ItemMetaData(movie.id)
|
||||||
|
' validate movie meta data
|
||||||
|
if not isValid(movieMetaData)
|
||||||
|
stopLoadingSpinner()
|
||||||
|
return invalid
|
||||||
|
end if
|
||||||
|
' start building MovieDetails view
|
||||||
group = CreateObject("roSGNode", "MovieDetails")
|
group = CreateObject("roSGNode", "MovieDetails")
|
||||||
group.overhangTitle = movie.title
|
group.overhangTitle = movie.title
|
||||||
group.optionsAvailable = false
|
group.optionsAvailable = false
|
||||||
m.global.sceneManager.callFunc("pushScene", group)
|
|
||||||
|
|
||||||
movieMetaData = ItemMetaData(movie.id)
|
|
||||||
group.itemContent = movieMetaData
|
|
||||||
group.trailerAvailable = false
|
group.trailerAvailable = false
|
||||||
|
' push scene asap (to prevent extra button presses when retriving series/movie info)
|
||||||
|
m.global.sceneManager.callFunc("pushScene", group)
|
||||||
|
group.itemContent = movieMetaData
|
||||||
|
' local trailers
|
||||||
trailerData = api_API().users.getlocaltrailers(get_setting("active_user"), movie.id)
|
trailerData = api_API().users.getlocaltrailers(get_setting("active_user"), movie.id)
|
||||||
if isValid(trailerData)
|
if isValid(trailerData)
|
||||||
group.trailerAvailable = trailerData.Count() > 0
|
group.trailerAvailable = trailerData.Count() > 0
|
||||||
end if
|
end if
|
||||||
|
' watch for button presses
|
||||||
buttons = group.findNode("buttons")
|
buttons = group.findNode("buttons")
|
||||||
for each b in buttons.getChildren(-1, 0)
|
for each b in buttons.getChildren(-1, 0)
|
||||||
b.observeField("buttonSelected", m.port)
|
b.observeField("buttonSelected", m.port)
|
||||||
end for
|
end for
|
||||||
|
' setup and load movie extras
|
||||||
extras = group.findNode("extrasGrid")
|
extras = group.findNode("extrasGrid")
|
||||||
extras.observeField("selectedItem", m.port)
|
extras.observeField("selectedItem", m.port)
|
||||||
extras.callFunc("loadParts", movieMetaData.json)
|
extras.callFunc("loadParts", movieMetaData.json)
|
||||||
|
' done building MovieDetails view
|
||||||
stopLoadingSpinner()
|
stopLoadingSpinner()
|
||||||
return group
|
return group
|
||||||
end function
|
end function
|
||||||
|
|
||||||
function CreateSeriesDetailsGroup(series)
|
function CreateSeriesDetailsGroup(series as object) as dynamic
|
||||||
|
' validate series node
|
||||||
|
if not isValid(series) or not isValid(series.id) then return invalid
|
||||||
|
|
||||||
startLoadingSpinner()
|
startLoadingSpinner()
|
||||||
|
' get series meta data
|
||||||
|
seriesMetaData = ItemMetaData(series.id)
|
||||||
|
' validate series meta data
|
||||||
|
if not isValid(seriesMetaData)
|
||||||
|
stopLoadingSpinner()
|
||||||
|
return invalid
|
||||||
|
end if
|
||||||
' Get season data early in the function so we can check number of seasons.
|
' Get season data early in the function so we can check number of seasons.
|
||||||
seasonData = TVSeasons(series.id)
|
seasonData = TVSeasons(series.id)
|
||||||
' Divert to season details if user setting goStraightToEpisodeListing is enabled and only one season exists.
|
' Divert to season details if user setting goStraightToEpisodeListing is enabled and only one season exists.
|
||||||
|
@ -367,38 +534,43 @@ function CreateSeriesDetailsGroup(series)
|
||||||
stopLoadingSpinner()
|
stopLoadingSpinner()
|
||||||
return CreateSeasonDetailsGroupByID(series.id, seasonData.Items[0].id)
|
return CreateSeasonDetailsGroupByID(series.id, seasonData.Items[0].id)
|
||||||
end if
|
end if
|
||||||
|
' start building SeriesDetails view
|
||||||
group = CreateObject("roSGNode", "TVShowDetails")
|
group = CreateObject("roSGNode", "TVShowDetails")
|
||||||
group.optionsAvailable = false
|
group.optionsAvailable = false
|
||||||
|
' push scene asap (to prevent extra button presses when retriving series/movie info)
|
||||||
m.global.sceneManager.callFunc("pushScene", group)
|
m.global.sceneManager.callFunc("pushScene", group)
|
||||||
|
group.itemContent = seriesMetaData
|
||||||
group.itemContent = ItemMetaData(series.id)
|
group.seasonData = seasonData
|
||||||
group.seasonData = seasonData ' Re-use variable from beginning of function
|
' watch for button presses
|
||||||
|
|
||||||
group.observeField("seasonSelected", m.port)
|
group.observeField("seasonSelected", m.port)
|
||||||
|
' setup and load series extras
|
||||||
extras = group.findNode("extrasGrid")
|
extras = group.findNode("extrasGrid")
|
||||||
extras.observeField("selectedItem", m.port)
|
extras.observeField("selectedItem", m.port)
|
||||||
extras.callFunc("loadParts", group.itemcontent.json)
|
extras.callFunc("loadParts", seriesMetaData.json)
|
||||||
|
' done building SeriesDetails view
|
||||||
stopLoadingSpinner()
|
stopLoadingSpinner()
|
||||||
return group
|
return group
|
||||||
end function
|
end function
|
||||||
|
|
||||||
' Shows details on selected artist. Bio, image, and list of available albums
|
' Shows details on selected artist. Bio, image, and list of available albums
|
||||||
function CreateArtistView(musicartist)
|
function CreateArtistView(artist as object) as dynamic
|
||||||
musicData = MusicAlbumList(musicartist.id)
|
' validate artist node
|
||||||
appearsOnData = AppearsOnList(musicartist.id)
|
if not isValid(artist) or not isValid(artist.id) then return invalid
|
||||||
|
|
||||||
|
musicData = MusicAlbumList(artist.id)
|
||||||
|
appearsOnData = AppearsOnList(artist.id)
|
||||||
|
|
||||||
if (musicData = invalid or musicData.Items.Count() = 0) and (appearsOnData = invalid or appearsOnData.Items.Count() = 0)
|
if (musicData = invalid or musicData.Items.Count() = 0) and (appearsOnData = invalid or appearsOnData.Items.Count() = 0)
|
||||||
' Just songs under artists...
|
' Just songs under artists...
|
||||||
group = CreateObject("roSGNode", "AlbumView")
|
group = CreateObject("roSGNode", "AlbumView")
|
||||||
group.pageContent = ItemMetaData(musicartist.id)
|
group.pageContent = ItemMetaData(artist.id)
|
||||||
|
|
||||||
' Lookup songs based on artist id
|
' Lookup songs based on artist id
|
||||||
songList = GetSongsByArtist(musicartist.id)
|
songList = GetSongsByArtist(artist.id)
|
||||||
|
|
||||||
if not isValid(songList)
|
if not isValid(songList)
|
||||||
' Lookup songs based on folder parent / child relationship
|
' Lookup songs based on folder parent / child relationship
|
||||||
songList = MusicSongList(musicartist.id)
|
songList = MusicSongList(artist.id)
|
||||||
end if
|
end if
|
||||||
|
|
||||||
if not isValid(songList)
|
if not isValid(songList)
|
||||||
|
@ -412,10 +584,10 @@ function CreateArtistView(musicartist)
|
||||||
else
|
else
|
||||||
' User has albums under artists
|
' User has albums under artists
|
||||||
group = CreateObject("roSGNode", "ArtistView")
|
group = CreateObject("roSGNode", "ArtistView")
|
||||||
group.pageContent = ItemMetaData(musicartist.id)
|
group.pageContent = ItemMetaData(artist.id)
|
||||||
group.musicArtistAlbumData = musicData
|
group.musicArtistAlbumData = musicData
|
||||||
group.musicArtistAppearsOnData = appearsOnData
|
group.musicArtistAppearsOnData = appearsOnData
|
||||||
group.artistOverview = ArtistOverview(musicartist.name)
|
group.artistOverview = ArtistOverview(artist.name)
|
||||||
|
|
||||||
group.observeField("musicAlbumSelected", m.port)
|
group.observeField("musicAlbumSelected", m.port)
|
||||||
group.observeField("playArtistSelected", m.port)
|
group.observeField("playArtistSelected", m.port)
|
||||||
|
@ -429,7 +601,10 @@ function CreateArtistView(musicartist)
|
||||||
end function
|
end function
|
||||||
|
|
||||||
' Shows details on selected album. Description text, image, and list of available songs
|
' Shows details on selected album. Description text, image, and list of available songs
|
||||||
function CreateAlbumView(album)
|
function CreateAlbumView(album as object) as dynamic
|
||||||
|
' validate album node
|
||||||
|
if not isValid(album) or not isValid(album.id) then return invalid
|
||||||
|
|
||||||
group = CreateObject("roSGNode", "AlbumView")
|
group = CreateObject("roSGNode", "AlbumView")
|
||||||
m.global.sceneManager.callFunc("pushScene", group)
|
m.global.sceneManager.callFunc("pushScene", group)
|
||||||
|
|
||||||
|
@ -449,12 +624,15 @@ function CreateAlbumView(album)
|
||||||
end function
|
end function
|
||||||
|
|
||||||
' Shows details on selected playlist. Description text, image, and list of available items
|
' Shows details on selected playlist. Description text, image, and list of available items
|
||||||
function CreatePlaylistView(album)
|
function CreatePlaylistView(playlist as object) as dynamic
|
||||||
|
' validate playlist node
|
||||||
|
if not isValid(playlist) or not isValid(playlist.id) then return invalid
|
||||||
|
|
||||||
group = CreateObject("roSGNode", "PlaylistView")
|
group = CreateObject("roSGNode", "PlaylistView")
|
||||||
m.global.sceneManager.callFunc("pushScene", group)
|
m.global.sceneManager.callFunc("pushScene", group)
|
||||||
|
|
||||||
group.pageContent = ItemMetaData(album.id)
|
group.pageContent = ItemMetaData(playlist.id)
|
||||||
group.albumData = PlaylistItemList(album.id)
|
group.albumData = PlaylistItemList(playlist.id)
|
||||||
|
|
||||||
' Watch for user clicking on an item
|
' Watch for user clicking on an item
|
||||||
group.observeField("playItem", m.port)
|
group.observeField("playItem", m.port)
|
||||||
|
@ -465,39 +643,66 @@ function CreatePlaylistView(album)
|
||||||
return group
|
return group
|
||||||
end function
|
end function
|
||||||
|
|
||||||
function CreateSeasonDetailsGroup(series, season)
|
function CreateSeasonDetailsGroup(series as object, season as object) as dynamic
|
||||||
|
' validate series node
|
||||||
|
if not isValid(series) or not isValid(series.id) then return invalid
|
||||||
|
' validate season node
|
||||||
|
if not isValid(season) or not isValid(season.id) then return invalid
|
||||||
|
|
||||||
startLoadingSpinner()
|
startLoadingSpinner()
|
||||||
|
' get season meta data
|
||||||
|
seasonMetaData = ItemMetaData(season.id)
|
||||||
|
' validate season meta data
|
||||||
|
if not isValid(seasonMetaData)
|
||||||
|
stopLoadingSpinner()
|
||||||
|
return invalid
|
||||||
|
end if
|
||||||
|
' start building SeasonDetails view
|
||||||
group = CreateObject("roSGNode", "TVEpisodes")
|
group = CreateObject("roSGNode", "TVEpisodes")
|
||||||
group.optionsAvailable = false
|
group.optionsAvailable = false
|
||||||
|
' push scene asap (to prevent extra button presses when retriving series/movie info)
|
||||||
m.global.sceneManager.callFunc("pushScene", group)
|
m.global.sceneManager.callFunc("pushScene", group)
|
||||||
|
group.seasonData = seasonMetaData.json
|
||||||
group.seasonData = ItemMetaData(season.id).json
|
|
||||||
group.objects = TVEpisodes(series.id, season.id)
|
group.objects = TVEpisodes(series.id, season.id)
|
||||||
|
' watch for button presses
|
||||||
group.observeField("episodeSelected", m.port)
|
group.observeField("episodeSelected", m.port)
|
||||||
group.observeField("quickPlayNode", m.port)
|
group.observeField("quickPlayNode", m.port)
|
||||||
|
' finished building SeasonDetails view
|
||||||
stopLoadingSpinner()
|
stopLoadingSpinner()
|
||||||
|
|
||||||
return group
|
return group
|
||||||
end function
|
end function
|
||||||
|
|
||||||
function CreateSeasonDetailsGroupByID(seriesID, seasonID)
|
function CreateSeasonDetailsGroupByID(seriesID as string, seasonID as string) as dynamic
|
||||||
|
' validate parameters
|
||||||
|
if seriesID = "" or seasonID = "" then return invalid
|
||||||
|
|
||||||
startLoadingSpinner()
|
startLoadingSpinner()
|
||||||
|
' get season meta data
|
||||||
|
seasonMetaData = ItemMetaData(seasonID)
|
||||||
|
' validate season meta data
|
||||||
|
if not isValid(seasonMetaData)
|
||||||
|
stopLoadingSpinner()
|
||||||
|
return invalid
|
||||||
|
end if
|
||||||
|
' start building SeasonDetails view
|
||||||
group = CreateObject("roSGNode", "TVEpisodes")
|
group = CreateObject("roSGNode", "TVEpisodes")
|
||||||
group.optionsAvailable = false
|
group.optionsAvailable = false
|
||||||
|
' push scene asap (to prevent extra button presses when retriving series/movie info)
|
||||||
m.global.sceneManager.callFunc("pushScene", group)
|
m.global.sceneManager.callFunc("pushScene", group)
|
||||||
|
group.seasonData = seasonMetaData.json
|
||||||
group.seasonData = ItemMetaData(seasonID).json
|
|
||||||
group.objects = TVEpisodes(seriesID, seasonID)
|
group.objects = TVEpisodes(seriesID, seasonID)
|
||||||
|
' watch for button presses
|
||||||
group.observeField("episodeSelected", m.port)
|
group.observeField("episodeSelected", m.port)
|
||||||
group.observeField("quickPlayNode", m.port)
|
group.observeField("quickPlayNode", m.port)
|
||||||
|
' finished building SeasonDetails view
|
||||||
stopLoadingSpinner()
|
stopLoadingSpinner()
|
||||||
return group
|
return group
|
||||||
end function
|
end function
|
||||||
|
|
||||||
function CreateItemGrid(libraryItem)
|
function CreateItemGrid(libraryItem as object) as dynamic
|
||||||
|
' validate libraryItem
|
||||||
|
if not isValid(libraryItem) then return invalid
|
||||||
|
|
||||||
group = CreateObject("roSGNode", "ItemGrid")
|
group = CreateObject("roSGNode", "ItemGrid")
|
||||||
group.parentItem = libraryItem
|
group.parentItem = libraryItem
|
||||||
group.optionsAvailable = true
|
group.optionsAvailable = true
|
||||||
|
@ -505,7 +710,10 @@ function CreateItemGrid(libraryItem)
|
||||||
return group
|
return group
|
||||||
end function
|
end function
|
||||||
|
|
||||||
function CreateMovieLibraryView(libraryItem)
|
function CreateMovieLibraryView(libraryItem as object) as dynamic
|
||||||
|
' validate libraryItem
|
||||||
|
if not isValid(libraryItem) then return invalid
|
||||||
|
|
||||||
group = CreateObject("roSGNode", "MovieLibraryView")
|
group = CreateObject("roSGNode", "MovieLibraryView")
|
||||||
group.parentItem = libraryItem
|
group.parentItem = libraryItem
|
||||||
group.optionsAvailable = true
|
group.optionsAvailable = true
|
||||||
|
@ -513,7 +721,10 @@ function CreateMovieLibraryView(libraryItem)
|
||||||
return group
|
return group
|
||||||
end function
|
end function
|
||||||
|
|
||||||
function CreateMusicLibraryView(libraryItem)
|
function CreateMusicLibraryView(libraryItem as object) as dynamic
|
||||||
|
' validate libraryItem
|
||||||
|
if not isValid(libraryItem) then return invalid
|
||||||
|
|
||||||
group = CreateObject("roSGNode", "MusicLibraryView")
|
group = CreateObject("roSGNode", "MusicLibraryView")
|
||||||
group.parentItem = libraryItem
|
group.parentItem = libraryItem
|
||||||
group.optionsAvailable = true
|
group.optionsAvailable = true
|
||||||
|
@ -530,13 +741,10 @@ function CreateSearchPage()
|
||||||
return group
|
return group
|
||||||
end function
|
end function
|
||||||
|
|
||||||
sub CreateSidePanel(buttons, options)
|
function CreateVideoPlayerGroup(video_id as string, mediaSourceId = invalid as dynamic, audio_stream_idx = 1 as integer, forceTranscoding = false as boolean, showIntro = true as boolean, allowResumeDialog = true as boolean)
|
||||||
group = CreateObject("roSGNode", "OptionsSlider")
|
' validate video_id
|
||||||
group.buttons = buttons
|
if not isValid(video_id) or video_id = "" then return invalid
|
||||||
group.options = options
|
|
||||||
end sub
|
|
||||||
|
|
||||||
function CreateVideoPlayerGroup(video_id, mediaSourceId = invalid, audio_stream_idx = 1, forceTranscoding = false, showIntro = true, allowResumeDialog = true)
|
|
||||||
startMediaLoadingSpinner()
|
startMediaLoadingSpinner()
|
||||||
' Video is Playing
|
' Video is Playing
|
||||||
video = VideoPlayer(video_id, mediaSourceId, audio_stream_idx, defaultSubtitleTrackFromVid(video_id), forceTranscoding, showIntro, allowResumeDialog)
|
video = VideoPlayer(video_id, mediaSourceId, audio_stream_idx, defaultSubtitleTrackFromVid(video_id), forceTranscoding, showIntro, allowResumeDialog)
|
||||||
|
@ -553,18 +761,29 @@ function CreateVideoPlayerGroup(video_id, mediaSourceId = invalid, audio_stream_
|
||||||
return video
|
return video
|
||||||
end function
|
end function
|
||||||
|
|
||||||
function CreatePersonView(personData as object) as object
|
function CreatePersonView(personData as object) as dynamic
|
||||||
startLoadingSpinner()
|
' validate personData node
|
||||||
person = CreateObject("roSGNode", "PersonDetails")
|
if not isValid(personData) or not isValid(personData.id) then return invalid
|
||||||
m.global.SceneManager.callFunc("pushScene", person)
|
|
||||||
|
|
||||||
info = ItemMetaData(personData.id)
|
startLoadingSpinner()
|
||||||
person.itemContent = info
|
' get person meta data
|
||||||
|
personMetaData = ItemMetaData(personData.id)
|
||||||
|
' validate season meta data
|
||||||
|
if not isValid(personMetaData)
|
||||||
stopLoadingSpinner()
|
stopLoadingSpinner()
|
||||||
|
return invalid
|
||||||
|
end if
|
||||||
|
' start building Person View
|
||||||
|
person = CreateObject("roSGNode", "PersonDetails")
|
||||||
|
' push scene asap (to prevent extra button presses when retriving series/movie info)
|
||||||
|
m.global.SceneManager.callFunc("pushScene", person)
|
||||||
|
person.itemContent = personMetaData
|
||||||
person.setFocus(true)
|
person.setFocus(true)
|
||||||
|
' watch for button presses
|
||||||
person.observeField("selectedItem", m.port)
|
person.observeField("selectedItem", m.port)
|
||||||
person.findNode("favorite-button").observeField("buttonSelected", m.port)
|
person.findNode("favorite-button").observeField("buttonSelected", m.port)
|
||||||
|
' finished building Person View
|
||||||
|
stopLoadingSpinner()
|
||||||
return person
|
return person
|
||||||
end function
|
end function
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
function VideoPlayer(id, mediaSourceId = invalid, audio_stream_idx = 1, subtitle_idx = -1, forceTranscoding = false, showIntro = true, allowResumeDialog = true)
|
function VideoPlayer(id as string, mediaSourceId = invalid as dynamic, audio_stream_idx = 1 as integer, subtitle_idx = -1 as integer, forceTranscoding = false as boolean, showIntro = true as boolean, allowResumeDialog = true as boolean) as dynamic
|
||||||
' Get video controls and UI
|
' Get video controls and UI
|
||||||
video = CreateObject("roSGNode", "JFVideo")
|
video = CreateObject("roSGNode", "JFVideo")
|
||||||
video.id = id
|
video.id = id
|
||||||
|
@ -20,7 +20,7 @@ function VideoPlayer(id, mediaSourceId = invalid, audio_stream_idx = 1, subtitle
|
||||||
return video
|
return video
|
||||||
end function
|
end function
|
||||||
|
|
||||||
sub AddVideoContent(video, mediaSourceId, audio_stream_idx = 1, subtitle_idx = -1, playbackPosition = -1, forceTranscoding = false, showIntro = true, allowResumeDialog = true)
|
sub AddVideoContent(video as object, mediaSourceId as dynamic, audio_stream_idx = 1 as integer, subtitle_idx = -1 as integer, playbackPosition = -1 as integer, forceTranscoding = false as boolean, showIntro = true as boolean, allowResumeDialog = true as boolean)
|
||||||
video.content = createObject("RoSGNode", "ContentNode")
|
video.content = createObject("RoSGNode", "ContentNode")
|
||||||
meta = ItemMetaData(video.id)
|
meta = ItemMetaData(video.id)
|
||||||
if meta = invalid
|
if meta = invalid
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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
|
|
|
@ -207,13 +207,13 @@ sub setFieldTextValue(field, value)
|
||||||
end sub
|
end sub
|
||||||
|
|
||||||
' Returns whether or not passed value is valid
|
' Returns whether or not passed value is valid
|
||||||
function isValid(input) as boolean
|
function isValid(input as dynamic) as boolean
|
||||||
return input <> invalid
|
return input <> invalid
|
||||||
end function
|
end function
|
||||||
|
|
||||||
' Returns whether or not passed value is valid and not empty
|
' Returns whether or not passed value is valid and not empty
|
||||||
' Accepts a string, or any countable type (arrays and lists)
|
' 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
|
if not isValid(input) then return false
|
||||||
' Use roAssociativeArray instead of list so we get access to the doesExist() method
|
' Use roAssociativeArray instead of list so we get access to the doesExist() method
|
||||||
countableTypes = { "array": 1, "list": 1, "roarray": 1, "roassociativearray": 1, "rolist": 1 }
|
countableTypes = { "array": 1, "list": 1, "roarray": 1, "roassociativearray": 1, "rolist": 1 }
|
||||||
|
|
4
test-app/manifest
Normal file
4
test-app/manifest
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
title=Rooibos Unit Testing
|
||||||
|
major_version=0
|
||||||
|
minor_version=1
|
||||||
|
build_version=1
|
18
test-app/source/BaseTestSuite.spec.bs
Normal file
18
test-app/source/BaseTestSuite.spec.bs
Normal 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
4
test-app/source/Main.bs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
function Main(args)
|
||||||
|
? "here is my code"
|
||||||
|
? "hello"
|
||||||
|
end function
|
188
test-app/source/tests/utils/misc/isValid.spec.bs
Normal file
188
test-app/source/tests/utils/misc/isValid.spec.bs
Normal 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
|
Loading…
Reference in New Issue
Block a user