Compare commits

..
155 Commits
Author SHA1 Message Date
Stasia Michalska c1b87080f1 Rename to @rebble/clay and pebble-dev/clay 2025-12-21 18:48:49 +01:00
Stasia Michalska b2e9ed2392 Commit the package-lock 2025-12-21 18:11:07 +01:00
Stasia Michalska 75548a1c04 Update dependencies 2025-12-21 18:11:07 +01:00
Stasia Michalska 900b03de1d Ignore dist and all waf build locks 2025-12-21 18:11:07 +01:00
Stasia Michalska cc0e88598f Replace travis with github actions 2025-12-21 18:11:07 +01:00
Stasia Michalska 078186cd25 Add flint support 2025-12-13 00:53:33 +01:00
Stasia Michalska b55a4c6c61 Fix tests 2025-12-12 23:58:27 +01:00
Stasia Michalska 4dad63ea90 Update to newer gulp version 2025-12-12 16:46:12 +01:00
Stasia Michalska 6335169e9a Remove node-sass requirement that crashes the build 2025-11-15 21:07:43 +01:00
Keegan Lillo 1bf6db0809 Release v1.0.4 (#156) 2016-11-21 12:09:01 -08:00
Keegan Lillo 4b53d920c7 #77 - Add docs for setSettings() (#155) 2016-11-21 11:32:53 -08:00
Keegan Lillo fa3406a809 #151 - Fix incorrect value being set when defaultValue is falsey (#152) 2016-10-21 12:11:12 -07:00
Yohan Robert 46c0adb62c #77 - Allow claySettings to be modified from app.js (#150)
* .setSettings(key, value) to set a single setting
* .setSettings(object) to set multiple settings at once
2016-10-21 10:36:55 -07:00
Keegan Lillo d18170337d Release/v1.0.3 (#148) 2016-10-18 09:47:32 -07:00
Keegan Lillo 9c7238d578 #124/get items by group (#147)
* Add getItemsByGroup function

* Update test

* update readme

* Update README for submit button

* Update docs with ID prop for button and submit components
2016-10-18 09:40:32 -07:00
Keegan Lillo 1a63761e43 #141 - Use index.js not app.js in docs (#142) 2016-09-29 01:15:45 -07:00
Ben Combee 9c6bb48c7a fix copy-paste error with messageKey in ClayItem description (#138) 2016-09-14 23:42:36 -07:00
Jon Barlow 2173ad7c71 Add some note about Rocky.js (#134) 2016-08-17 13:51:22 +01:00
Keegan Lillo b8db6c6a90 Direct pebble.js users to v0.1.7 and fix typos 2016-07-11 11:29:06 -07:00
Keegan Lillo 62f0337198 release/v1.0.2 (#129) 2016-07-08 15:28:29 -07:00
Grégoire Sage eb89496733 #123 - Add 'configurable' capability 2016-07-08 15:06:59 -07:00
Grégoire Sage 5d6e070e01 #108 Fix colorpicker for Diorite 2016-07-08 14:56:45 -07:00
Keegan Lillo ae31f9737f #120 - Fixed Error: Cannot find module 'message_keys' in dev script 2016-06-23 23:39:06 -07:00
Keegan Lillo 6e42537aa1 #118/Add Configurable should be checked to cloudpebble getting started (#119) 2016-06-23 11:57:56 -07:00
Keegan Lillo 2b14076171 #86/Added contribution instructions (#111)
* Added contribution instructions

* remove production mode in the contributing guide.

* Remove squashing commits from contributing guide

* Add better examples for style guide.

* Add getting help section

* Change devsupport email to twitter

* Add link to contributing guide from the main readme
2016-06-20 15:32:40 -07:00
Keegan Lillo 55ac8681af Merge pull request #115 from pebble/documentation-update
Remove straggling reference to appinfo.js from README
2016-06-19 17:19:45 -07:00
Keegan Lillo b5d2615307 Remove straggling reference to appinfo.js from README 2016-06-19 16:48:49 -07:00
Keegan Lillo 6742ec84cf Merge pull request #113 from pebble/update-docs-to-new-require-mothod
Update README.md
2016-06-19 13:32:26 -07:00
Keegan Lillo 61d735662e Update README.md 2016-06-19 13:28:15 -07:00
Keegan Lillo 050b89f82f Merge pull request #112 from pebble/#110/add-get-settings-to-migration-guide
#110/add get settings to migration guide
2016-06-18 00:59:41 -07:00
Keegan 1b179a603a Added the changed behavior of clay.getSettings() to migration guide 2016-06-18 00:55:15 -07:00
Keegan 4a6382aa7f Remove unused function parameter in clay.getSettings() 2016-06-18 00:50:53 -07:00
Keegan Lillo f0c44c5719 Merge pull request #107 from pebble/release/v1.0.1
Bump version to 1.0.1
2016-06-17 17:11:07 -07:00
Keegan 2c2076bf69 Bump version to 1.0.1 2016-06-17 17:07:29 -07:00
Keegan Lillo 5a67fec99e Merge pull request #106 from pebble/update-docs-with-new-array-syntax
Fixed some straggling mentions of the old array behavior
2016-06-16 16:42:19 -07:00
Keegan 7cadd5fba4 update JSDoc on the Clay.prepareForAppMessage() method 2016-06-16 14:55:42 -07:00
Keegan Lillo 6b59844084 Fixed some straggling mentions of the old array behavior 2016-06-16 14:52:31 -07:00
keegan-lillo b3963179d1 Merge pull request #105 from pebble/release/v1.0.0
Release 1.0.0
2016-06-16 11:32:22 -07:00
keegan-lillo 6a63c40e08 Release 1.0.0 2016-06-16 11:28:34 -07:00
keegan-lillo f91c525a30 Merge pull request #104 from pebble/#100/migration-guide
Add migration guide
2016-06-16 11:27:46 -07:00
Keegan 19779a627f add li9nk for migration guide 2016-06-16 11:22:31 -07:00
Keegan b19ad435df Add migration guide 2016-06-16 11:21:00 -07:00
keegan-lillo 5627f789a3 Merge pull request #103 from pebble/#90/support-message-keys
#90/support message keys
2016-06-16 00:55:54 -07:00
Keegan 1c9a812009 add build folder to .eslintignore 2016-06-16 00:48:29 -07:00
Keegan 939398774d remove another impossible case 2016-06-16 00:45:52 -07:00
Keegan 361dc99ea5 remove undefined check from Clay.prepareSettingsForAppMessage because it is impossible 2016-06-16 00:40:10 -07:00
keegan-lillo 9f2a89a41e Merge branch 'master' into #90/support-message-keys 2016-06-16 00:35:27 -07:00
Keegan 26df41b25e Document messageKey array syntax 2016-06-16 00:34:42 -07:00
Keegan dd6fe54834 allow array syntax inside of the messageKey property of a config item 2016-06-15 23:58:41 -07:00
Keegan 3cffa3a275 Send messageKeys down to appMessage in the new format 2016-06-15 21:50:36 -07:00
Keegan a0335be710 Checkbox groups now return an array of booleans 2016-06-15 14:20:35 -07:00
Keegan cabee3ae0b Yell at developer if they try and use appKeys 2016-06-15 11:22:16 -07:00
Keegan 940c5e2451 Rename all appKey references to messageKey 2016-06-15 10:58:45 -07:00
keegan-lillo 870436ddd8 Merge pull request #102 from pebble/#96/fix-webviewclosed-data-being-double-decoded
Allow response in webviewclosed event to optionally be URI encoded
2016-06-14 23:02:06 -07:00
Keegan c3c8de77fc Allow response in webviewclosed event to optionally be URI encoded 2016-06-14 22:57:36 -07:00
keegan-lillo 87807f180c Merge pull request #101 from pebble/#98/expose-clay-version
Expose Clay version
2016-06-14 22:04:15 -07:00
Keegan 9e344d527b Expose Clay version 2016-06-14 21:59:10 -07:00
keegan-lillo 2f9e36bdb2 Merge pull request #99 from pebble/#97/pebble-package
Make Clay a Pebble package
2016-06-14 21:49:38 -07:00
Keegan 5038e80bf2 remove "main" property of package.json 2016-06-14 21:30:40 -07:00
Keegan 7a2dbb276f use messageKey instead of appKey and clarify 3.13 gotchas 2016-06-14 21:30:17 -07:00
Keegan 3300e98ce2 add src/js/index.js to .eslintignore 2016-06-14 20:55:13 -07:00
Keegan 693b795c2b Add a few more files to what is included on NPM to make debugging easier 2016-06-14 18:31:11 -07:00
Keegan ef8ab37eb4 Update the readme to include instructions for sdk 3.13 2016-06-14 18:24:43 -07:00
Keegan 118df1d952 Make Clay a Pebble package 2016-06-14 17:02:01 -07:00
Cherie Williams 8d4ff52b0f Merge pull request #91 from pebble/update-readme-for-3.13
Update README for loader.js changes in SDK 3.13
2016-06-09 16:51:57 -07:00
keegan-lillo 613c41fd6e Merge branch 'master' into update-readme-for-3.13 2016-06-09 14:38:06 -07:00
Cherie Williams ecac278f63 Make SDK 3.13 notice a header & add error sample 2016-06-09 14:21:10 -07:00
keegan-lillo 742890ee31 Merge pull request #94 from pebble/#66/update-readme
Add JS settings to getting started
2016-06-09 09:01:27 -07:00
keegan-lillo f08f11fb6f Merge branch 'master' into #66/update-readme 2016-06-08 18:51:20 -07:00
keegan-lillo 859e2f2767 Merge pull request #93 from pebble/#69/remove-attributes-references-in-docs
Remove reference to "attributes" from docs
2016-06-08 18:51:08 -07:00
keegan-lillo 19ad09eb66 Update README.md 2016-06-08 18:49:55 -07:00
keegan-lillo 39056a2868 Remove reference to "attributes" from docs 2016-06-08 18:34:11 -07:00
Cherie Williams 8a2d560482 Update README for loader.js changes in SDK 3.13 2016-06-08 17:56:56 -07:00
keegan-lillo f62f960433 Merge pull request #89 from pebble/release/v0.1.7
Bump version to v1.0.7
2016-05-31 10:20:13 -07:00
keegan-lillo 8fb67ce08d Bump version to v1.0.7 2016-05-31 10:11:55 -07:00
keegan-lillo 65b952d7f8 Merge pull request #88 from pebble/#87/allow-not-prefix-for-capabilities
Allow `NOT_` prefix for capabilities
2016-05-31 10:06:22 -07:00
Keegan 6c2a335b7c Allow NOT_ prefix for capabilities 2016-05-31 09:57:10 -07:00
keegan-lillo f717661eea Merge pull request #85 from pebble/release/0.1.6
Bump version to 0.1.6
2016-05-30 22:10:12 -07:00
keegan-lillo 35bdeb7519 Bump version to 0.1.6 2016-05-30 22:06:54 -07:00
keegan-lillo 93b927db63 Merge pull request #83 from pebble/#18/show-items-based-on-capability
Add the option to only show items with particular capabilities
2016-05-30 22:00:33 -07:00
Keegan 9afdd2adb9 add warning about capabilities 2016-05-30 21:54:16 -07:00
Keegan 5fe87a04de Add the option to only show items with particular capabilities 2016-05-30 20:30:17 -07:00
keegan-lillo 7e2b75dd58 Merge pull request #82 from pebble/#10/remove-old-settings
Only send settings that are actually in the config back to the watch
2016-05-29 11:41:54 -07:00
Keegan 32e192e938 Only send settings that are actually in the config back to the watch 2016-05-29 11:38:00 -07:00
keegan-lillo 1af2b39db1 Merge pull request #81 from pebble/#72/att-optgroup-to-selects
Add optgroup to selects
2016-05-28 14:57:16 -07:00
Keegan 782eaf82f3 Document optgroups for selects 2016-05-28 14:50:06 -07:00
Keegan 41d38e04a7 Allow optgroups for selects 2016-05-28 14:41:45 -07:00
keegan-lillo 1611599a25 Merge pull request #80 from pebble/#74/fix-select-styling-on-windows
fix styling of selects on windows
2016-05-28 14:19:16 -07:00
keegan-lillo 842cbc9e84 Merge branch 'master' into #74/fix-select-styling-on-windows 2016-05-28 14:15:45 -07:00
keegan-lillo 238d0e6a6c Merge pull request #79 from pebble/#70/fix-time-date-styling-on-ios
fix styling of time and date fields on iOS
2016-05-28 14:14:38 -07:00
Keegan c5af3baaeb fix styling of selects on windows 2016-05-28 14:12:31 -07:00
Keegan 2030aa1af7 fix styling of time and date fields on iOS 2016-05-28 13:52:27 -07:00
keegan-lillo 4445cb98cf Merge pull request #78 from pebble/#76/wipe-page-on-build
#76/wipe page on build
2016-05-28 13:26:22 -07:00
Keegan 12c420d861 make ClayConfig.destroy() return self 2016-05-28 13:11:53 -07:00
Keegan dc33aa0b5d update docs 2016-05-28 13:09:04 -07:00
keegan-lillo f200af909b Merge branch 'master' into #76/wipe-page-on-build 2016-05-28 13:02:34 -07:00
Keegan 6f3873ad6b add tests for new clayConfig.build() behavior 2016-05-28 13:00:55 -07:00
Keegan 70bb2e9d07 Add destroy function 2016-05-26 18:45:22 -07:00
keegan-lillo a9aaf0b940 Merge pull request #68 from ishotjr/ishotjr-appinfo
📝 Update README to include required appinfo.json changes
2016-04-04 08:56:36 -07:00
Ish Ot Jr 80eb1506d8 📝 Update README to include required appinfo.json changes 2016-04-03 12:14:41 -04:00
keegan-lillo 68723d36f7 Merge pull request #67 from gregoiresage/master
Always display all the decimals of the slider's value
2016-03-28 13:34:22 -07:00
Grégoire Sage c937ee735e always display all the decimals of the slider's value
Add test
2016-03-28 22:20:23 +02:00
keegan-lillo b553094742 Merge pull request #64 from pebble/release/v0.1.5
Bump Version to v0.1.5
2016-03-21 11:28:57 -07:00
keegan-lillo f31ffed558 Bump Version to v0.1.5 2016-03-21 09:26:59 -07:00
keegan-lillo 3f71f97b8b Merge pull request #62 from pebble/#61/fix-color-picker-default
add default background color to value display in color picker
2016-03-21 09:25:52 -07:00
Keegan 1a6308f621 add default background color to value display in color picker 2016-03-20 11:16:03 -07:00
keegan-lillo 7045c59a72 Merge pull request #60 from pebble/release/v0.1.4
Bump version to v0.1.4
2016-03-16 12:09:29 +11:00
keegan-lillo 4ffb1018f7 Bump version to v0.1.4 2016-03-16 12:04:19 +11:00
keegan-lillo e78c09f5be Merge pull request #54 from pebble/#52/custom-color-picker-layout
#52/custom color picker layout
2016-03-16 12:03:13 +11:00
keegan-lillo d8e73f2674 Merge branch 'master' into #52/custom-color-picker-layout 2016-03-16 11:54:18 +11:00
keegan-lillo 0358e820e6 Merge pull request #59 from pebble/document-pebblejs
Document Pebble.js usage
2016-03-16 11:54:04 +11:00
keegan-lillo 1d821f61c3 Change Settings.data to Settings.option 2016-03-16 11:44:50 +11:00
keegan-lillo e2ffa42595 Document Pebble.js usage 2016-03-16 11:42:39 +11:00
keegan-lillo 3a2dcb187e Merge branch 'master' into #52/custom-color-picker-layout 2016-03-16 09:12:39 +11:00
keegan-lillo 08e3a21429 Merge pull request #56 from pebble/#55/unicode-support
Allow unicode characters in config
2016-03-16 09:12:30 +11:00
keegan-lillo 42a613caf4 Merge branch 'master' into #52/custom-color-picker-layout 2016-03-16 08:25:59 +11:00
keegan-lillo 4ff38eb0ba Merge branch 'master' into #55/unicode-support 2016-03-16 08:25:44 +11:00
keegan-lillo b466c48487 Merge pull request #48 from pebble/#47/range-slider-component
#47/range slider component
2016-03-16 08:25:06 +11:00
keegan-lillo 517d4f84ff Add quotes to slider description 2016-03-16 08:12:13 +11:00
keegan-lillo e04a6c9f87 Merge branch 'master' into #47/range-slider-component 2016-03-15 13:11:32 +11:00
keegan-lillo 1f0867985d Merge branch 'master' into #52/custom-color-picker-layout 2016-03-15 13:11:24 +11:00
keegan-lillo d0e5988024 Merge branch 'master' into #55/unicode-support 2016-03-15 13:11:16 +11:00
keegan-lillo d007d48afc Merge pull request #57 from pebble/document-clayconfig-param-in-component-initialize
Document the clayConfig parameter of custom component initializer
2016-03-15 13:11:09 +11:00
keegan-lillo 07a847457f Document the clayConfig parameter of custom component initializer 2016-03-15 13:07:19 +11:00
Keegan 9347c9f3d2 Allow unicode characters in config 2016-03-15 12:06:04 +11:00
Keegan 4ee5a7361c When setting the value for the color picker, round it to the closest available value in the layout 2016-03-15 04:53:58 +11:00
Keegan dad8b2a025 add allowGray option to color picker 2016-03-14 03:09:23 +11:00
Keegan d52f6d4aa2 Globally increase mocha timeout because Travis struggles sometimes 2016-03-14 01:55:22 +11:00
keegan-lillo a94eae9a16 Clarify acceptable default values for the color picker. 2016-03-14 01:30:41 +11:00
keegan-lillo 317f6fd4e8 Grammar fixes in color picker docs 2016-03-14 01:23:46 +11:00
keegan-lillo b19b07c47d Fix syntax error in Color Picker examples 2016-03-14 01:20:07 +11:00
Keegan 0a2ddab597 Document new color picker functionality 2016-03-14 01:02:40 +11:00
Keegan 005ac939b1 Refine color picker styles 2016-03-14 01:01:58 +11:00
Keegan 12b0ae9bd6 Allow custom layouts in the color picker and automatically choose a layout based on platform 2016-03-14 01:01:13 +11:00
Keegan 6f92c0d267 Merge remote-tracking branch 'origin/master' into #47/range-slider-component
# Conflicts:
#	index.js
2016-03-12 10:27:27 +11:00
keegan-lillo 756cdc0d9e Merge pull request #50 from pebble/#20/allow-user-data
#20/allow user data
2016-03-12 10:25:04 +11:00
Keegan 783bc84779 fix typo in Clay JSdoc 2016-03-12 10:22:01 +11:00
Keegan e621bbf0bd Merge remote-tracking branch 'origin/master' into #20/allow-user-data 2016-03-12 10:16:47 +11:00
keegan-lillo 43c5c3cc9f Merge pull request #51 from pebble/#39/fix-event-targets
Fix events firing on wrong element for button component
2016-03-12 10:15:37 +11:00
Keegan d8738e06b2 fix tests for slider 2016-03-11 11:12:00 +11:00
Keegan 67449ec34e Use number for value display in slider because of vendor implementation inconsistencies 2016-03-11 11:04:52 +11:00
keegan-lillo 5c63b00eda Make slider update value display as user is adjusting it 2016-03-11 10:49:23 +11:00
Keegan 79eef99731 Fix events firing on wrong element for button component 2016-03-11 03:57:30 +11:00
Keegan e5496c62d6 Add developers to send arbitrary data to the config page 2016-03-11 03:36:00 +11:00
Keegan b1a6f184ba Stop exposing config as a reference. resolves #35 2016-03-11 02:25:59 +11:00
Keegan 878fea0340 Allow floats in the slider component and update docs 2016-03-11 01:07:17 +11:00
Keegan 1142d1adee Renamed ClayConfig.getSettings() to ClayConfig.serialize() 2016-03-10 21:00:44 +11:00
Keegan 0873f5eba2 Added Slider component 2016-03-10 04:20:50 +11:00
Keegan 0faa585d5f Clean up manipulators tests 2016-03-10 00:03:50 +11:00
keegan-lillo 3b7c4cda7c Merge pull request #42 from pebble/fix-readme-getsetting-link
Fix link in readme
2016-03-09 15:41:01 +11:00
keegan-lillo 3839a47f5c Merge branch 'master' into fix-readme-getsetting-link 2016-03-09 15:37:16 +11:00
keegan-lillo 85503d2846 Merge pull request #46 from clach04/readme_json_v_js
Clarify config.json versus config.js differences
2016-03-09 15:35:30 +11:00
clach04 d6ae6faef4 Merge branch 'master' into readme_json_v_js 2016-03-08 18:06:23 -08:00
clach04 2c2f003e15 Clarify config.json versus config.js differences
Make example config clear, main impact is for CloudPebble usage where json is not yet an option.
2016-03-08 18:02:49 -08:00
keegan-lillo 1af5828b18 Update README.md 2016-03-09 12:32:51 +11:00
60 changed files with 17278 additions and 837 deletions
+2
View File
@@ -1,4 +1,6 @@
coverage/
dist/
tmp/
build/
src/scripts/vendor/
src/js/index.js
+1 -1
View File
@@ -9,7 +9,7 @@
},
"rules": {
"new-cap": [0],
"max-params": [2, 4],
"max-params": [2, 5],
"require-jsdoc": [2, {
"require": {
"FunctionDeclaration": true,
+11
View File
@@ -0,0 +1,11 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
- package-ecosystem: "npm" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "daily"
+29
View File
@@ -0,0 +1,29 @@
name: Build CI
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
container:
image: ghcr.io/pebble-dev/pebbleos-docker:v1
steps:
- uses: actions/checkout@v4
- name: Set up uv
uses: astral-sh/setup-uv@v7
- run: uv tool install pebble-tool
- run: npm install
- run: npm run build
- run: pebble build
- uses: actions/upload-artifact@v4
with:
name: clay
path: dist.zip
+23
View File
@@ -0,0 +1,23 @@
name: Lint CI
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: 'npm'
- run: npm install
- run: npm run lint
+28
View File
@@ -0,0 +1,28 @@
name: Node.js Package
on:
release:
types: [created]
permissions:
id-token: write
contents: read
jobs:
publish-npm:
runs-on: ubuntu-latest
container:
image: ghcr.io/pebble-dev/pebbleos-docker:v1
steps:
- uses: actions/checkout@v4
- name: Set up uv
uses: astral-sh/setup-uv@v7
- run: uv tool install pebble-tool
- run: npm install
- run: npm run build
- run: pebble build
- name: Update npm
run: npm install -g npm@latest
- run: npm publish
+34
View File
@@ -0,0 +1,34 @@
name: Test CI
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: 'npm'
- name: Setup Chrome
uses: browser-actions/setup-chrome@v2
id: setup-chrome
with:
install-dependencies: true
- run: npm install
- run: npm run build
- name: Run headless test
uses: coactions/setup-xvfb@v1
with:
run: npm run test-travis
env:
CHROME_BIN: ${{ steps.setup-chrome.outputs.chrome-path }}
+9 -23
View File
@@ -1,31 +1,17 @@
# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
/coverage
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
/.lock-waf_*_build
# Dependency directory
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
node_modules
/node_modules
/npm-debug.log
tmp
dist
/tmp/
/src/js/index.js
/dist.zip
/dist/
/build/
Executable
+5
View File
@@ -0,0 +1,5 @@
# We ignore everying except for what is actually used by the pebble tool.
/**/*
!/dist.zip
!/src/**/*
!/index.js
-12
View File
@@ -1,12 +0,0 @@
language: node_js
sudo: false
node_js:
- '4'
before_script:
# Needed to include Chrome in Travis for karma tests
- export CHROME_BIN=chromium-browser
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
script: 'npm run test-travis'
+198
View File
@@ -0,0 +1,198 @@
# Contributing
Thanks for checking out Clay. We accept pull requests from anybody, however we ask
that you follow some conventions when contributing to Clay.
We have set up a
[piece-of-cake](https://github.com/pebble-dev/clay/issues?q=is%3Aopen+is%3Aissue+label%3Apiece-of-cake)
label in GitHub issues for bugs/features that are easy to implement. These issues are
a great place to start for people that are new to the codebase.
## Requirements
- [Node](https://nodejs.org) (4.0 and later)
- [Google Chrome](www.google.com/chrome) (To run tests)
- [Pebble SDK](https://developer.rebble.io/sdk/) (optional)
## Getting Started
### 1. Fork, then clone the repo:
`$ git clone git@github.com:your-username/clay.git`
### 2. Create a new branch with the following naming convention.
`#<issue-id>/description-of-change`
example:
`#90/support-message-keys`
If there is currently no GitHub issue open for your proposed contribution,
then please make one. This allows us to assign milestones and triage priority.
### 3. Install the project dependencies.
`$ npm install`
### 4. Develop your feature
Commit as often as you like. Your branch will be squashed when the pull request is merged.
### 5. Run the tests
`$ npm run test`
### 6. Run linting
`$ npm run lint`
### 7. Submit your pull request
In the main comment of the pull request, you must state the GitHub issue that your PR
resolves. You do this by using the syntax `Resolves #<issue-id>`. An example of this
can be found [here](https://github.com/pebble/clay/pull/81)
# Project Structure and Development
## Important Files
There are two main entry points for Clay - `index.js` and `src/scripts/config-page.js`.
### index.js
This is the main entry point for the code that will run in the Pebble app's `src/js/app.js`.
It is responsible for serializing the provided config into a data URI that will be opened
using `Pebble.openURL()`. It also persists data to `localStorage`.
### src/scripts/config-page.js
This is the main entry point for the code that runs on the generated config page,
and is responsible for passing the injected config and other components to the
`ClayConfig` class.
## Building
Use the following command to build Clay during development.
`$ npm run dev`
This command packages up `src/scripts/config-page.js` and `dev/dev.js` into the `tmp/` directory
so `dev/dev.html` can include them as script tags. This will also watch for changes
in the project.
While developing components and other functionality for Clay, it is much easier to
work with the files in the `dev/` directory than on a phone or emulator. Below is
an explanation of the files and their purpose.
| File | Purpose |
|----------|-----|
| `dev.html` | Open this page in a browser after running `$ npm run dev`. |
| `dev.js` | Injects the components and dependencies into the window the same way `index.js` would. |
| `config.js` | Clay config to use as a sandbox for testing components. |
| `custom-fn.js` | Clay custom function to be injected by `dev.js`. |
| `emulator.html` | Copy of the HTML page that is used to make the Clay compatible with the Pebble SDK emulator. |
| `uri-test.html` | Used to stress test URI creation for older browser versions. |
## Testing your change inside of a test project.
Once your change is ready to be tested in the emulator or on the watch, follow these steps:
1. From your Clay fork run `$ npm run pebble-build`. This builds Clay to work with the Pebble SDK
2. From your test Pebble project, run `$ pebble package install /path/to/your/clay/fork`.
This will modify your `package.json` to point directly to your local copy of Clay.
3. Run the usual`$ pebble build && pebble install --emulator basalt && pebble emu-app-config` to test your modifications
4. If you make modifications to your fork of Clay, you must repeat the above steps.
## Functionality
Most of the magic happens in the `src/scripts/lib` directory. `config-page.js`
initializes a new instance of `ClayConfig` and calls the injected custom function
(`window.customFn`) with the `ClayConfig` as its context. This allows developers to
add extra functionality to the config page, such as setting values of items dynamically
or registering small custom components.
Once the `ClayConfig` is initialized, we run the `.build()` method. This iterates over
the config and injects each item into the page. Each item is an instance of `ClayItem`.
It also indexes the items to later be retrieved with `.getAllItems()`,
`.getItemByMessageKey()`, `.getItemById()`, `.getItemsByType()`.
## Testing
Clay enforces 100% test coverage. Chances are, that if you are submitting a pull request,
there should be tests for the proposed code changes. Have a look at the other tests in
the repository to see some examples of how you should be writing your tests.
There are two commands to use while writing your tests.
- `$ npm run test` - Runs all tests once.
- `$ npm run test-debug` - Watches for file changes and re-runs all tests when it
detects a change. This also leaves an in instance of Google Chrome running.
If you click the "Debug" button in the top right of the page, a new tab will open.
You can then use the Google Chrome developer tools to debug your tests as they run.
Refreshing the page will re-run the tests.
## Style Guide
Our style guide is very similar to the
[Airbnb style guide.](https://github.com/airbnb/javascript/tree/master/es5) with the
following exceptions:
- When saving a reference to `this` use `self`.
```javascript
// bad
function() {
var _this = this;
return function() {
console.log(_this);
};
}
// bad
function() {
var that = this;
return function() {
console.log(that);
};
}
// good
function() {
var self = this;
return function() {
console.log(self);
};
}
```
- Don't double name your functions. It makes refactoring more difficult.
```javascript
// bad
var log = function log(msg) {
console.log(msg);
};
// good
var log = function(msg) {
console.log(msg);
};
```
The style guide is enforced using ESLint. Run `$ npm run lint` to ensure you are
adhering to the style guide.
## Getting Help
- Chat to us in the `#app-dev` channel on Discord. https://discordapp.com/invite/aRUAYFN
- Visit the [Rebble Forums](https://forum.rebble.io/)
- Skeet at [@rebble.io](https://bsky.app/profile/rebble.io)
- More options at https://developer.rebble.io/community/online/
+548 -157
View File
File diff suppressed because it is too large Load Diff
+38
View File
@@ -0,0 +1,38 @@
'use strict';
/* eslint-disable quotes */
module.exports = [
{
"type": "heading",
"id": "main-heading",
"defaultValue": "Clay Test Page",
"size": 1
},
{
"type": "text",
"defaultValue": "This is config 2"
},
{
"type": "section",
"items": [
{
"type": "heading",
"defaultValue": "This is a section"
},
{
"type": "input",
"messageKey": "email",
"defaultValue": "",
"label": "Input Field",
"description": "This is a new field",
"attributes": {
"placeholder": "Placeholder set with attributes"
}
}
]
},
{
"type": "submit",
"defaultValue": "Save"
}
];
+69 -14
View File
@@ -13,6 +13,17 @@ module.exports = [
"defaultValue": "Some arbitrary text explaining how this all works. " +
"It's cool if this wraps across multiple lines"
},
{
"type": "section",
"capabilities": ["RECT", "MICROPHONE"],
"items": [
{
"type": "text",
"defaultValue": "This section is only visible for rectangular devices that" +
" have a microphone"
}
]
},
{
"type": "section",
"items": [
@@ -20,9 +31,14 @@ module.exports = [
"type": "heading",
"defaultValue": "This is a section"
},
{
"type": "text",
"defaultValue": "This is only visible for aplite",
"capabilities": ["APLITE"]
},
{
"type": "input",
"appKey": "email",
"messageKey": "email",
"defaultValue": "",
"label": "Input Field",
"description": "This is a description for the input component. " +
@@ -31,22 +47,58 @@ module.exports = [
"placeholder": "Placeholder set with attributes"
}
},
{
"type": "input",
"label": "Time Field",
"attributes": {
"type": "time"
}
},
{
"type": "input",
"label": "Date Field",
"attributes": {
"type": "date"
}
},
{
"type": "slider",
"messageKey": "slider",
"defaultValue": 0,
"label": "Slider",
"description": "This is the description for the slider",
"min": 0,
"max": 30,
"step": 0.25
},
{
"type": "toggle",
"appKey": "cool_stuff",
"messageKey": "cool_stuff",
"label": "This is a Toggle",
"defaultValue": false
},
{
"type": "toggle",
"messageKey": "arrayToggle[0]",
"label": "This is a Toggle",
"defaultValue": false
},
{
"type": "toggle",
"messageKey": "arrayToggle[1]",
"label": "This is a Toggle",
"defaultValue": false
},
{
"type": "color",
"appKey": "colorTest",
"messageKey": "colorTest",
"defaultValue": "FF0000",
"label": "Standard Color Picker",
"sunlight": false
},
{
"type": "color",
"appKey": "sunnyColorTest",
"messageKey": "sunnyColorTest",
"defaultValue": "00FF00",
"label": "Sunny Color Picker",
"sunlight": true
@@ -70,7 +122,7 @@ module.exports = [
},
{
"type": "radiogroup",
"appKey": "radiogroup-test",
"messageKey": "radiogroup-test",
"label": "Radio Group",
"options": [
{ "label": "Test thing", "value": "one" },
@@ -80,25 +132,28 @@ module.exports = [
},
{
"type": "checkboxgroup",
"appKey": "checkboxgroup-test",
"defaultValue": ["quote' \"test", "two"],
"messageKey": "checkboxgroup-test",
"defaultValue": [0, 1, 0],
"label": "Checkbox Group",
"options": [
{ "label": "First thing", "value": "three" },
{ "label": "Another thing", "value": "two" },
{ "label": "Final thing", "value": "three" }
]
"options": [ "First thing", "Another thing", "Final thing" ]
},
{
"type": "select",
"appKey": "flavor",
"messageKey": "flavor",
"defaultValue": "grape",
"label": "Select Menu",
"options": [
{ "label": "", "value": "" },
{ "label": "Berry", "value": "berry" },
{ "label": "This Option is Selected", "value": "grape" },
{ "label": "Banana", "value": "banana" }
{ "label": "Banana", "value": "banana" },
{
"label": "This is an optgroup",
"value": [
{ "label": "Peach", "value": "peach" },
{ "label": "Mango", "value": "mango" }
]
}
],
"attributes": {
"required": "required"
+15 -3
View File
@@ -10,15 +10,27 @@ module.exports = function() {
*/
function toggleBackground() {
if (this.get()) {
Clay.getItemByAppKey('colorTest').enable();
Clay.getItemByMessageKey('colorTest').enable();
} else {
Clay.getItemByAppKey('colorTest').disable();
Clay.getItemByMessageKey('colorTest').disable();
}
}
/**
* @returns {void}
*/
function handleButtonClick() {
Clay.config = Clay.meta.userData.config2;
Clay.build();
}
Clay.on(Clay.EVENTS.AFTER_BUILD, function() {
var coolStuffToggle = Clay.getItemByAppKey('cool_stuff');
var coolStuffToggle = Clay.getItemByMessageKey('cool_stuff');
toggleBackground.call(coolStuffToggle);
coolStuffToggle.on('change', toggleBackground);
Clay.getItemById('testButton').on('click', handleButtonClick);
});
console.log('userData: ', Clay.meta.userData);
};
+17
View File
@@ -5,6 +5,23 @@ window.clayConfig = require('./config.js');
window.claySettings = {};
window.customFn = require('./custom-fn.js');
window.clayComponents = require('../src/scripts/components');
window.clayMeta = require('../test/fixture').meta({
activeWatchInfo: {
platform: 'basalt',
model: 'qemu_platform_basalt',
language: 'en_US',
firmware: {
major: 3,
minor: 10,
patch: 2,
suffix: ''
}
},
userData: {
foo: 'bar',
config2: require('./config-2')
}
});
var platform = window.navigator.userAgent.match(/Android/) ? 'android' : 'ios';
document.documentElement.classList.add('platform-' + platform);
+6 -1
View File
@@ -20,9 +20,14 @@
var returnTo = getQueryParam('return_to');
var data = decodeURIComponent(location.hash.substring(1));
if (data) {
// Check if we dealing with a base64 encoded URI
if (data && data.charAt(0) !== '<') {
data = window.atob(data).replace('$$RETURN_TO$$', returnTo);
window.location.href = 'data:text/html;base64,' + encodeURIComponent(window.btoa(data));
} else if (data) {
data = data.replace('$$RETURN_TO$$', returnTo);
window.location.href = 'data:text/html;charset=utf-8,' + encodeURIComponent(data);
}
</script>
<body>
+62 -37
View File
@@ -8,65 +8,71 @@ var stringify = require('stringify');
var del = require('del');
var inline = require('gulp-inline');
var htmlmin = require('gulp-htmlmin');
var sass = require('gulp-sass');
var sass = require('gulp-sass')(require('sass'));
var sourceMaps = require('gulp-sourcemaps');
var autoprefixer = require('gulp-autoprefixer');
var uglify = require('gulp-uglify');
var sassify = require('sassify');
var autoprefixify = require('./src/scripts/vendor/autoprefixify');
var insert = require('gulp-insert');
var clayPackage = require('./package.json');
var sassIncludePaths = [].concat(
require('bourbon').includePaths,
'src/styles'
);
var sassifyOptions = {
base64Encode: false,
sourceMap: false,
sourceMapEmbed: false,
sourceMapContents: false,
outputStyle: 'compact',
includePaths: sassIncludePaths
};
var autoprefixerOptions = {
browsers: ['Android 4', 'iOS 8'],
cascade: false
};
var stringifyOptions = ['.html', '.tpl'];
var versionMessage = '/* Clay - https://github.com/pebble/clay - Version: ' +
require('./package.json').version +
var stringifyOptions = ['.html', '.tpl', '.css'];
var versionMessage = '/* Clay - https://github.com/pebble-dev/clay - Version: ' +
clayPackage.version +
' - Build Date: ' + new Date().toISOString() + ' */\n';
gulp.task('clean-js', function() {
return del(['tmp/config-page.js']);
});
gulp.task('js', ['clean-js'], function() {
/**
* @returns {string}
*/
function taskJs() {
return browserify('src/scripts/config-page.js', { debug: true })
.transform('deamdify')
.bundle()
.pipe(source('config-page.js'))
.pipe(gulp.dest('./tmp/'));
});
}
gulp.task('js', gulp.series('clean-js', taskJs));
gulp.task('clean-sass', function() {
return del(['tmp/config-page.scss']);
});
gulp.task('sass', ['clean-sass'], function() {
gulp.src('./src/styles/config-page.scss')
/**
* @returns {string}
*/
function taskSass() {
return gulp.src(['./src/styles/config-page.scss',
'./src/styles/clay/components/*.scss'])
.pipe(sourceMaps.init())
.pipe(sass({
includePaths: sassIncludePaths
loadPaths: sassIncludePaths
}).on('error', sass.logError))
.pipe(autoprefixer(autoprefixerOptions))
.pipe(sourceMaps.write('./'))
.pipe(gulp.dest('tmp'));
});
}
gulp.task('inlineHtml', ['js', 'sass'], function() {
gulp.task('sass', gulp.series('clean-sass', taskSass));
/**
* @returns {string}
*/
function taskInlineHtml() {
return gulp.src('src/config-page.html')
.pipe(inline())
.pipe(htmlmin({
@@ -79,41 +85,60 @@ gulp.task('inlineHtml', ['js', 'sass'], function() {
minifyCSS: true
}))
.pipe(gulp.dest('tmp/'));
});
}
gulp.task('clay', ['inlineHtml'], function() {
gulp.task('inlineHtml', gulp.series('js', 'sass', taskInlineHtml));
/**
* @returns {string}
*/
function taskClay() {
return browserify('index.js', {
debug: false,
standalone: 'clay'
standalone: clayPackage.name
})
.transform('deamdify')
.transform(stringify(stringifyOptions))
.transform(sassify, sassifyOptions)
.transform(autoprefixify, autoprefixerOptions)
.require(require.resolve('./index'), {expose: 'pebble-clay'})
// .transform(autoprefixify, autoprefixerOptions)
.require(require.resolve('./index'), {expose: clayPackage.name})
.exclude('message_keys')
.bundle()
.pipe(source('clay.js'))
.pipe(source('index.js'))
.pipe(buffer())
.pipe(uglify())
.pipe(uglify({
preserveComments: 'license'
}))
.pipe(insert.prepend(versionMessage))
.pipe(gulp.dest('./dist/'));
});
.pipe(gulp.dest('./src/js'));
}
gulp.task('dev-js', ['js', 'sass'], function() {
gulp.task('clay', gulp.series('inlineHtml', taskClay));
/**
* @returns {string}
*/
function taskDevJs() {
return browserify('dev/dev.js', { debug: true })
.transform(stringify(stringifyOptions))
.transform('deamdify')
.transform(sassify, sassifyOptions)
.transform(autoprefixify, autoprefixerOptions)
.ignore('message_keys')
.bundle()
.pipe(source('dev.js'))
.pipe(gulp.dest('./tmp/'));
});
}
gulp.task('default', ['clay']);
gulp.task('dev-js', gulp.series('js', 'sass', taskDevJs));
gulp.task('dev', ['dev-js'], function() {
gulp.task('default', gulp.series('clay'));
/**
* @returns {string}
*/
function taskDev() {
gulp.watch('src/styles/**/*.scss', ['sass']);
gulp.watch(['src/scripts/**/*.js', 'src/templates/**/*.tpl'], ['js']);
gulp.watch(['src/**', 'dev/**/*.js'], ['dev-js']);
});
}
gulp.task('dev', gulp.series('dev-js', taskDev));
+214 -62
View File
@@ -3,15 +3,19 @@
var configPageHtml = require('./tmp/config-page.html');
var toSource = require('tosource');
var standardComponents = require('./src/scripts/components');
var utils = require('./src/scripts/lib/utils');
var deepcopy = require('deepcopy/build/deepcopy.min');
var version = require('./package.json').version;
var messageKeys = require('message_keys');
/**
* @param {Array} config - the Clay config
* @param {function} [customFn] - Custom code to run from the config page. Will run
* with the ClayConfig instance as context
* @param {Object} [options] - Additional options to pass to Clay
* @param {boolean} [options.autoHandleEvents] - If false, Clay will not
* @param {boolean} [options.autoHandleEvents=true] - If false, Clay will not
* automatically handle the 'showConfiguration' and 'webviewclosed' events
* @param {*} [options.userData={}] - Arbitrary data to pass to the config page. Will
* be available as `clayConfig.meta.userData`
* @constructor
*/
function Clay(config, customFn, options) {
@@ -27,14 +31,30 @@ function Clay(config, customFn, options) {
options = options || {};
self.config = config;
self.config = deepcopy(config);
self.customFn = customFn || function() {};
self.components = {};
self.meta = {
activeWatchInfo: null,
accountToken: '',
watchToken: ''
watchToken: '',
userData: {}
};
self.version = version;
/**
* Populate the meta with data from the Pebble object. Make sure to run this inside
* either the "showConfiguration" or "ready" event handler
* @return {void}
*/
function _populateMeta() {
self.meta = {
activeWatchInfo: Pebble.getActiveWatchInfo && Pebble.getActiveWatchInfo(),
accountToken: Pebble.getAccountToken(),
watchToken: Pebble.getWatchToken(),
userData: deepcopy(options.userData || {})
};
}
// Let Clay handle all the magic
if (options.autoHandleEvents !== false && typeof Pebble !== 'undefined') {
@@ -63,36 +83,50 @@ function Clay(config, customFn, options) {
}
/**
* Populate the meta with data from the Pebble object. Make sure to run this inside
* either the "showConfiguration" or "ready" event handler
* @return {void}
* If this function returns true then the callback will be executed
* @callback _scanConfig_testFn
* @param {Clay~ConfigItem} item
*/
function _populateMeta() {
self.meta = {
activeWatchInfo: Pebble.getActiveWatchInfo && Pebble.getActiveWatchInfo(),
accountToken: Pebble.getAccountToken(),
watchToken: Pebble.getWatchToken()
};
}
/**
* @callback _scanConfig_callback
* @param {Clay~ConfigItem} item
*/
/**
* Scan over the config and run the callback if the testFn resolves to true
* @private
* @param {Clay~ConfigItem|Array} item
* @param {_scanConfig_testFn} testFn
* @param {_scanConfig_callback} callback
* @return {void}
*/
function _registerStandardComponents(item) {
function _scanConfig(item, testFn, callback) {
if (Array.isArray(item)) {
item.forEach(function(item) {
_registerStandardComponents(item);
_scanConfig(item, testFn, callback);
});
} else if (item.type === 'section') {
_registerStandardComponents(item.items);
} else if (standardComponents[item.type]) {
self.registerComponent(standardComponents[item.type]);
_scanConfig(item.items, testFn, callback);
} else if (testFn(item)) {
callback(item);
}
}
_registerStandardComponents(self.config);
// register standard components
_scanConfig(self.config, function(item) {
return standardComponents[item.type];
}, function(item) {
self.registerComponent(standardComponents[item.type]);
});
// validate config against teh use of appKeys
_scanConfig(self.config, function(item) {
return item.appKey;
}, function() {
throw new Error('appKeys are no longer supported. ' +
'Please follow the migration guide to upgrade your project');
});
}
/**
@@ -156,72 +190,190 @@ Clay.prototype.generateUrl = function() {
Clay.prototype.getSettings = function(response, convert) {
// Decode and parse config data as JSON
var settings = {};
response = response.match(/^\{/) ? response : decodeURIComponent(response);
try {
settings = JSON.parse(decodeURIComponent(response));
settings = JSON.parse(response);
} catch (e) {
throw new Error('The provided response was not valid JSON');
}
localStorage.setItem('clay-settings', JSON.stringify(settings));
// flatten the settings for localStorage
var settingsStorage = {};
Object.keys(settings).forEach(function(key) {
if (typeof settings[key] === 'object' && settings[key]) {
settingsStorage[key] = settings[key].value;
} else {
settingsStorage[key] = settings[key];
}
});
return convert === false ? settings : utils.prepareSettingsForAppMessage(settings);
localStorage.setItem('clay-settings', JSON.stringify(settingsStorage));
return convert === false ? settings : Clay.prepareSettingsForAppMessage(settings);
};
/**
* Updates the settings with the given value(s).
*
* @signature `clay.setSettings(key, value)`
* @param {String} key - The property to set.
* @param {*} value - the value assigned to _key_.
* @return {undefined}
*
* @signature `clay.setSettings(settings)`
* @param {Object} settings - an object containing the key/value pairs to be set.
* @return {undefined}
*/
Clay.prototype.setSettings = function(key, value) {
var settingsStorage = {};
try {
settingsStorage = JSON.parse(localStorage.getItem('clay-settings')) || {};
} catch (e) {
console.error(e.toString());
}
if (typeof key === 'object') {
var settings = key;
Object.keys(settings).forEach(function(key) {
settingsStorage[key] = settings[key];
});
} else {
settingsStorage[key] = value;
}
localStorage.setItem('clay-settings', JSON.stringify(settingsStorage));
};
/**
* @param {string} input
* @param {string} [prefix]
* @param {string} [prefix='data:text/html;charset=utf-8,']
* @returns {string}
*/
Clay.encodeDataUri = function(input, prefix) {
prefix = typeof prefix !== 'undefined' ? prefix : 'data:text/html;base64,';
prefix = typeof prefix !== 'undefined' ? prefix : 'data:text/html;charset=utf-8,';
return prefix + encodeURIComponent(input);
};
if (window.btoa) {
return prefix + encodeURIComponent(window.btoa(input));
/**
* Converts the val into a type compatible with Pebble.sendAppMessage().
* - Strings will be returned without modification
* - Numbers will be returned without modification
* - Booleans will be converted to a 0 or 1
* - Arrays that contain strings will be returned without modification
* eg: ['one', 'two'] becomes ['one', 'two']
* - Arrays that contain numbers will be returned without modification
* eg: [1, 2] becomes [1, 2]
* - Arrays that contain booleans will be converted to a 0 or 1
* eg: [true, false] becomes [1, 0]
* - Arrays must be single dimensional
* - Objects that have a "value" property will apply the above rules to the type of
* value. If the value is a number or an array of numbers and the optional
* property: "precision" is provided, then the number will be multiplied by 10 to
* the power of precision (value * 10 ^ precision) and then floored.
* Eg: 1.4567 with a precision set to 3 will become 1456
* @param {number|string|boolean|Array|Object} val
* @param {number|string|boolean|Array} val.value
* @param {number|undefined} [val.precision=0]
* @returns {number|string|Array}
*/
Clay.prepareForAppMessage = function(val) {
/**
* moves the decimal place of a number by precision then drop any remaining decimal
* places.
* @param {number} number
* @param {number} precision - number of decimal places to move
* @returns {number}
* @private
*/
function _normalizeToPrecision(number, precision) {
return Math.floor(number * Math.pow(10, precision || 0));
}
// iOS doesn't have a window so we need to polyfil window.btoa
// extracted from https://github.com/inexorabletash/polyfill
var B64_ALPHABET =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
input = String(input);
var position = 0;
var out = [];
var o1;
var o2;
var o3;
var e1;
var e2;
var e3;
var e4;
var result;
if (/[^\x00-\xFF]/.test(input)) { throw Error('InvalidCharacterError'); }
if (Array.isArray(val)) {
result = [];
val.forEach(function(item, index) {
result[index] = Clay.prepareForAppMessage(item);
});
} else if (typeof val === 'object' && val) {
if (typeof val.value === 'number') {
result = _normalizeToPrecision(val.value, val.precision);
} else if (Array.isArray(val.value)) {
result = val.value.map(function(item) {
if (typeof item === 'number') {
return _normalizeToPrecision(item, val.precision);
}
return item;
});
} else {
result = Clay.prepareForAppMessage(val.value);
}
} else if (typeof val === 'boolean') {
result = val ? 1 : 0;
} else {
result = val;
}
while (position < input.length) {
o1 = input.charCodeAt(position++);
o2 = input.charCodeAt(position++);
o3 = input.charCodeAt(position++);
return result;
};
// 111111 112222 222233 333333
e1 = o1 >> 2;
e2 = ((o1 & 0x3) << 4) | (o2 >> 4);
e3 = ((o2 & 0xf) << 2) | (o3 >> 6);
e4 = o3 & 0x3f;
/**
* Converts a Clay settings dict into one that is compatible with
* Pebble.sendAppMessage(); It also uses the provided messageKeys to correctly
* assign arrays into individual keys
* @see {prepareForAppMessage}
* @param {Object} settings
* @returns {{}}
*/
Clay.prepareSettingsForAppMessage = function(settings) {
if (position === input.length + 2) {
e3 = 64;
e4 = 64;
} else if (position === input.length + 1) {
e4 = 64;
// flatten settings
var flatSettings = {};
Object.keys(settings).forEach(function(key) {
var val = settings[key];
var matches = key.match(/(.+?)(?:\[(\d*)\])?$/);
if (!matches[2]) {
flatSettings[key] = val;
return;
}
out.push(B64_ALPHABET.charAt(e1),
B64_ALPHABET.charAt(e2),
B64_ALPHABET.charAt(e3),
B64_ALPHABET.charAt(e4));
}
var position = parseInt(matches[2], 10);
key = matches[1];
return prefix + encodeURIComponent(out.join(''));
if (typeof flatSettings[key] === 'undefined') {
flatSettings[key] = [];
}
flatSettings[key][position] = val;
});
var result = {};
Object.keys(flatSettings).forEach(function(key) {
var messageKey = messageKeys[key];
var settingArr = Clay.prepareForAppMessage(flatSettings[key]);
settingArr = Array.isArray(settingArr) ? settingArr : [settingArr];
settingArr.forEach(function(setting, index) {
result[messageKey + index] = setting;
});
});
// validate the settings
Object.keys(result).forEach(function(key) {
if (Array.isArray(result[key])) {
throw new Error('Clay does not support 2 dimensional arrays for item ' +
'values. Make sure you are not attempting to use array ' +
'syntax (eg: "myMessageKey[2]") in the messageKey for ' +
'components that return an array, such as a checkboxgroup');
}
});
return result;
};
module.exports = Clay;
+13728
View File
File diff suppressed because it is too large Load Diff
+50 -31
View File
@@ -1,72 +1,91 @@
{
"name": "pebble-clay",
"version": "0.1.3",
"name": "@rebble/clay",
"version": "1.0.5",
"description": "Pebble Config Framework",
"main": "dist/clay.js",
"directories": {
"example": "example"
},
"scripts": {
"test-travis": "./node_modules/.bin/gulp && ./node_modules/.bin/karma start ./test/karma.conf.js --single-run --browsers chromeTravisCI && ./node_modules/.bin/eslint ./",
"test-travis": "./node_modules/.bin/gulp && ./node_modules/.bin/karma start ./test/karma.conf.js --single-run --browsers chromeTravisCI",
"test-debug": "(export DEBUG=true && ./node_modules/.bin/gulp && ./node_modules/.bin/karma start ./test/karma.conf.js --no-single-run)",
"test": "./node_modules/.bin/gulp && ./node_modules/.bin/karma start ./test/karma.conf.js --single-run",
"lint": "./node_modules/.bin/eslint ./",
"build": "gulp",
"dev": "gulp dev"
"dev": "gulp dev",
"pebble-clean": "rm -rf tmp src/js/index.js && pebble clean",
"pebble-publish": "npm run pebble-clean && npm run build && pebble build && pebble package publish && npm run pebble-clean",
"pebble-build": "npm run build && pebble build"
},
"repository": {
"type": "git",
"url": "git+https://github.com/pebble/clay.git"
"url": "git+https://github.com/pebble-dev/clay.git"
},
"keywords": [
"Pebble",
"Config"
"pebble",
"config",
"configuration",
"pebble-package"
],
"author": "Pebble Technology",
"license": "MIT",
"bugs": {
"url": "https://github.com/pebble/clay/issues"
"url": "https://github.com/pebble-dev/clay/issues"
},
"homepage": "https://github.com/pebble/clay#readme",
"pebble": {
"projectType": "package",
"sdkVersion": "3",
"targetPlatforms": [
"aplite",
"basalt",
"chalk",
"diorite",
"emery",
"flint"
],
"resources": {
"media": []
},
"capabilities": [
"configurable"
]
},
"homepage": "https://github.com/pebble-dev/clay#readme",
"devDependencies": {
"autoprefixer": "^6.3.1",
"autoprefixer": "^10.4.22",
"bourbon": "^4.2.6",
"browserify": "^13.0.0",
"browserify-istanbul": "^0.2.1",
"browserify-istanbul": "^3.0.1",
"chai": "^3.4.1",
"deamdify": "^0.2.0",
"deepcopy": "^0.6.1",
"del": "^2.0.2",
"eslint": "^1.5.1",
"eslint-config-pebble": "^1.2.0",
"eslint-plugin-standard": "^1.3.1",
"gulp": "^3.9.0",
"gulp": "^5.0.1",
"gulp-autoprefixer": "^3.1.0",
"gulp-htmlmin": "^1.3.0",
"gulp-inline": "0.0.15",
"gulp-inline": "^0.1.3",
"gulp-insert": "^0.5.0",
"gulp-sass": "^2.1.1",
"gulp-sass": "^6.0.1",
"gulp-sourcemaps": "^1.6.0",
"gulp-uglify": "^1.5.2",
"joi": "^6.10.1",
"karma": "^0.13.19",
"karma-browserify": "^5.0.1",
"karma-chrome-launcher": "^0.2.2",
"karma-coverage": "^0.5.3",
"karma-mocha": "^0.2.1",
"karma-mocha-reporter": "^1.1.5",
"karma-source-map-support": "^1.1.0",
"karma": "^6.4.0",
"karma-browserify": "^8.1.0",
"karma-chrome-launcher": "^3.2.0",
"karma-coverage": "^2.2.1",
"karma-mocha": "^2.0.1",
"karma-mocha-reporter": "^2.2.5",
"karma-source-map-support": "^1.4.0",
"karma-threshold-reporter": "^0.1.15",
"mocha": "^2.3.4",
"postcss": "^5.0.14",
"mocha": "^11.7.5",
"postcss": "^8.5.6",
"require-from-string": "^1.1.0",
"sassify": "^0.9.1",
"sass": "^1.94.2",
"sinon": "^1.17.3",
"stringify": "^3.2.0",
"through": "^2.3.8",
"tosource": "^1.0.0",
"vinyl-buffer": "^1.0.0",
"vinyl-source-stream": "^1.1.0",
"vinyl-source-stream": "^2.0.0",
"watchify": "^3.7.0"
},
"dependencies": {}
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

After

Width:  |  Height:  |  Size: 135 KiB

+1 -1
View File
@@ -3,7 +3,7 @@
module.exports = {
name: 'button',
template: require('../../templates/components/button.tpl'),
style: require('../../styles/clay/components/button.scss'),
style: require('../../../tmp/button.css'),
manipulator: 'button',
defaults: {
primary: false,
+2 -3
View File
@@ -3,12 +3,11 @@
module.exports = {
name: 'checkboxgroup',
template: require('../../templates/components/checkboxgroup.tpl'),
style: require('../../styles/clay/components/checkboxgroup.scss'),
style: require('../../../tmp/checkboxgroup.css'),
manipulator: 'checkboxgroup',
defaults: {
label: '',
options: [],
description: '',
attributes: {}
description: ''
}
};
+226 -93
View File
@@ -3,123 +3,233 @@
module.exports = {
name: 'color',
template: require('../../templates/components/color.tpl'),
style: require('../../styles/clay/components/color.scss'),
style: require('../../../tmp/color.css'),
manipulator: 'color',
defaults: {
label: '',
description: ''
},
initialize: function(minified) {
initialize: function(minified, clay) {
var HTML = minified.HTML;
var self = this;
var useSunlight = self.config.sunlight !== false;
var sunlightColorMap = {
'000000': '000000',
'000055': '001e41',
'0000aa': '004387',
'0000ff': '0068ca',
'005500': '2b4a2c',
'005555': '27514f',
'0055aa': '16638d',
'0055ff': '007dce',
'00aa00': '5e9860',
'00aa55': '5c9b72',
'00aaaa': '57a5a2',
'00aaff': '4cb4db',
'00ff00': '8ee391',
'00ff55': '8ee69e',
'00ffaa': '8aebc0',
'00ffff': '84f5f1',
'550000': '4a161b',
'550055': '482748',
'5500aa': '40488a',
'5500ff': '2f6bcc',
'555500': '564e36',
'555555': '545454',
'5555aa': '4f6790',
'5555ff': '4180d0',
'55aa00': '759a64',
'55aa55': '759d76',
'55aaaa': '71a6a4',
'55aaff': '69b5dd',
'55ff00': '9ee594',
'55ff55': '9de7a0',
'55ffaa': '9becc2',
'55ffff': '95f6f2',
'aa0000': '99353f',
'aa0055': '983e5a',
'aa00aa': '955694',
'aa00ff': '8f74d2',
'aa5500': '9d5b4d',
'aa5555': '9d6064',
'aa55aa': '9a7099',
'aa55ff': '9587d5',
'aaaa00': 'afa072',
'aaaa55': 'aea382',
'aaaaaa': 'ababab',
'ffffff': 'ffffff',
'aaaaff': 'a7bae2',
'aaff00': 'c9e89d',
'aaff55': 'c9eaa7',
'aaffaa': 'c7f0c8',
'aaffff': 'c3f9f7',
'ff0000': 'e35462',
'ff0055': 'e25874',
'ff00aa': 'e16aa3',
'ff00ff': 'de83dc',
'ff5500': 'e66e6b',
'ff5555': 'e6727c',
'ff55aa': 'e37fa7',
'ff55ff': 'e194df',
'ffaa00': 'f1aa86',
'ffaa55': 'f1ad93',
'ffaaaa': 'efb5b8',
'ffaaff': 'ecc3eb',
'ffff00': 'ffeeab',
'ffff55': 'fff1b5',
'ffffaa': 'fff6d3'
};
/**
* @param {string|boolean|number} color
* @returns {string}
*/
function cssColor(color) {
if (color === false) { return 'transparent'; }
if (typeof color === 'number') {
color = color.toString(16);
} else if (!color) {
return 'transparent';
}
while (color.length < 6) {
color = '0' + color;
}
color = padColorString(color);
return '#' + (useSunlight ? sunlightColorMap[color] : color);
}
/**
* @param {string} color
* @return {string}
*/
function padColorString(color) {
color = color.toLowerCase();
while (color.length < 6) {
color = '0' + color;
}
return color;
}
/**
* @param {number|string} value
* @returns {string|*}
*/
function normalizeColor(value) {
switch (typeof value) {
case 'number': return padColorString(value.toString(16));
case 'string': return value.replace(/^#|^0x/, '');
default: return value;
}
}
/**
* @param {Array.<Array>} layout
* @returns {Array}
*/
function flattenLayout(layout) {
return layout.reduce(function(a, b) {
return a.concat(b);
}, []);
}
/**
* Convert HEX color to LAB.
* Adapted from: https://github.com/antimatter15/rgb-lab
* @param {string} hex
* @returns {Array} - [l, a, b]
*/
function hex2lab(hex) {
hex = hex.replace(/^#|^0x/, '');
var r = parseInt(hex.slice(0, 2), 16) / 255;
var g = parseInt(hex.slice(2, 4), 16) / 255;
var b = parseInt(hex.slice(4), 16) / 255;
r = (r > 0.04045) ? Math.pow((r + 0.055) / 1.055, 2.4) : r / 12.92;
g = (g > 0.04045) ? Math.pow((g + 0.055) / 1.055, 2.4) : g / 12.92;
b = (b > 0.04045) ? Math.pow((b + 0.055) / 1.055, 2.4) : b / 12.92;
var x = (r * 0.4124 + g * 0.3576 + b * 0.1805) / 0.95047;
var y = (r * 0.2126 + g * 0.7152 + b * 0.0722) / 1.00000;
var z = (r * 0.0193 + g * 0.1192 + b * 0.9505) / 1.08883;
x = (x > 0.008856) ? Math.pow(x, 1 / 3) : (7.787 * x) + 16 / 116;
y = (y > 0.008856) ? Math.pow(y, 1 / 3) : (7.787 * y) + 16 / 116;
z = (z > 0.008856) ? Math.pow(z, 1 / 3) : (7.787 * z) + 16 / 116;
return [(116 * y) - 16, 500 * (x - y), 200 * (y - z)];
}
/**
* Find the perceptual color distance between two LAB colors
* @param {Array} labA
* @param {Array} labB
* @returns {number}
*/
function deltaE(labA, labB) {
var deltaL = labA[0] - labB[0];
var deltaA = labA[1] - labB[1];
var deltaB = labA[2] - labB[2];
return Math.sqrt(Math.pow(deltaL, 2) +
Math.pow(deltaA, 2) +
Math.pow(deltaB, 2));
}
/**
* Returns the layout based on the connected watch
* @returns {Array}
*/
function autoLayout() {
var bwWatches = ['aplite', 'diorite', 'flint'];
if (!clay.meta.activeWatchInfo ||
clay.meta.activeWatchInfo.firmware.major === 2 ||
bwWatches.indexOf(clay.meta.activeWatchInfo.platform) > -1 &&
!self.config.allowGray) {
return standardLayouts.BLACK_WHITE;
}
if (bwWatches.indexOf(clay.meta.activeWatchInfo.platform) > -1 &&
self.config.allowGray) {
return standardLayouts.GRAY;
}
return standardLayouts.COLOR;
}
/**
* @param {number|string} color
* @return {number}
*/
self.roundColorToLayout = function(color) {
var itemValue = normalizeColor(color);
// if the color is not in the layout we will need find the closest match
if (colorList.indexOf(itemValue) === -1) {
var itemValueLAB = hex2lab(itemValue);
var differenceArr = colorList.map(function(color) {
var colorLAB = hex2lab(normalizeColor(color));
return deltaE(itemValueLAB, colorLAB);
});
// Get the lowest number from the differenceArray
var lowest = Math.min.apply(Math, differenceArr);
// Get the index for that lowest number
var index = differenceArr.indexOf(lowest);
itemValue = colorList[index];
}
return parseInt(itemValue, 16);
};
var useSunlight = self.config.sunlight !== false;
var sunlightColorMap = {
'000000': '000000', '000055': '001e41', '0000aa': '004387', '0000ff': '0068ca',
'005500': '2b4a2c', '005555': '27514f', '0055aa': '16638d', '0055ff': '007dce',
'00aa00': '5e9860', '00aa55': '5c9b72', '00aaaa': '57a5a2', '00aaff': '4cb4db',
'00ff00': '8ee391', '00ff55': '8ee69e', '00ffaa': '8aebc0', '00ffff': '84f5f1',
'550000': '4a161b', '550055': '482748', '5500aa': '40488a', '5500ff': '2f6bcc',
'555500': '564e36', '555555': '545454', '5555aa': '4f6790', '5555ff': '4180d0',
'55aa00': '759a64', '55aa55': '759d76', '55aaaa': '71a6a4', '55aaff': '69b5dd',
'55ff00': '9ee594', '55ff55': '9de7a0', '55ffaa': '9becc2', '55ffff': '95f6f2',
'aa0000': '99353f', 'aa0055': '983e5a', 'aa00aa': '955694', 'aa00ff': '8f74d2',
'aa5500': '9d5b4d', 'aa5555': '9d6064', 'aa55aa': '9a7099', 'aa55ff': '9587d5',
'aaaa00': 'afa072', 'aaaa55': 'aea382', 'aaaaaa': 'ababab', 'ffffff': 'ffffff',
'aaaaff': 'a7bae2', 'aaff00': 'c9e89d', 'aaff55': 'c9eaa7', 'aaffaa': 'c7f0c8',
'aaffff': 'c3f9f7', 'ff0000': 'e35462', 'ff0055': 'e25874', 'ff00aa': 'e16aa3',
'ff00ff': 'de83dc', 'ff5500': 'e66e6b', 'ff5555': 'e6727c', 'ff55aa': 'e37fa7',
'ff55ff': 'e194df', 'ffaa00': 'f1aa86', 'ffaa55': 'f1ad93', 'ffaaaa': 'efb5b8',
'ffaaff': 'ecc3eb', 'ffff00': 'ffeeab', 'ffff55': 'fff1b5', 'ffffaa': 'fff6d3'
};
/* eslint-disable comma-spacing, no-multi-spaces, max-len,
standard/array-bracket-even-spacing */
var layout = self.config.layout || [
[false , false , '55ff00', 'aaff55', false , 'ffff55', 'ffffaa', false , false ],
[false , 'aaffaa', '55ff55', '00ff00', 'aaff00', 'ffff00', 'ffaa55', 'ffaaaa', false ],
['55ffaa', '00ff55', '00aa00', '55aa00', 'aaaa55', 'aaaa00', 'ffaa00', 'ff5500', 'ff5555'],
['aaffff', '00ffaa', '00aa55', '55aa55', '005500', '555500', 'aa5500', 'ff0000', 'ff0055'],
[false , '55aaaa', '00aaaa', '005555', 'ffffff', '000000', 'aa5555', 'aa0000', false ],
['55ffff', '00ffff', '00aaff', '0055aa', 'aaaaaa', '555555', '550000', 'aa0055', 'ff55aa'],
['55aaff', '0055ff', '0000ff', '0000aa', '000055', '550055', 'aa00aa', 'ff00aa', 'ffaaff'],
[false , '5555aa', '5555ff', '5500ff', '5500aa', 'aa00ff', 'ff00ff', 'ff55ff', false ],
[false , false , false , 'aaaaff', 'aa55ff', 'aa55aa', false , false , false ]
];
standard/array-bracket-even-spacing */
var standardLayouts = {
COLOR: [
[false , false , '55ff00', 'aaff55', false , 'ffff55', 'ffffaa', false , false ],
[false , 'aaffaa', '55ff55', '00ff00', 'aaff00', 'ffff00', 'ffaa55', 'ffaaaa', false ],
['55ffaa', '00ff55', '00aa00', '55aa00', 'aaaa55', 'aaaa00', 'ffaa00', 'ff5500', 'ff5555'],
['aaffff', '00ffaa', '00aa55', '55aa55', '005500', '555500', 'aa5500', 'ff0000', 'ff0055'],
[false , '55aaaa', '00aaaa', '005555', 'ffffff', '000000', 'aa5555', 'aa0000', false ],
['55ffff', '00ffff', '00aaff', '0055aa', 'aaaaaa', '555555', '550000', 'aa0055', 'ff55aa'],
['55aaff', '0055ff', '0000ff', '0000aa', '000055', '550055', 'aa00aa', 'ff00aa', 'ffaaff'],
[false , '5555aa', '5555ff', '5500ff', '5500aa', 'aa00ff', 'ff00ff', 'ff55ff', false ],
[false , false , false , 'aaaaff', 'aa55ff', 'aa55aa', false , false , false ]
],
GRAY: [
['000000', 'aaaaaa', 'ffffff']
],
BLACK_WHITE: [
['000000', 'ffffff']
]
};
/* eslint-enable */
var layout = self.config.layout || autoLayout();
if (typeof layout === 'string') {
layout = standardLayouts[layout];
}
// make sure layout is a 2D array
if (!Array.isArray(layout[0])) {
layout = [layout];
}
var colorList = flattenLayout(layout).map(function(item) {
return normalizeColor(item);
}).filter(function(item) {
return item;
});
var grid = '';
var itemWidth = 100 / layout[0].length;
var itemHeight = 100 / layout.length;
var rows = layout.length;
var cols = 0;
layout.forEach(function(row) {
cols = row.length > cols ? row.length : cols;
});
var itemWidth = 100 / cols;
var itemHeight = 100 / rows;
var $elem = self.$element;
for (var i = 0; i < layout.length; i++) {
for (var j = 0; j < layout[i].length; j++) {
for (var i = 0; i < rows; i++) {
for (var j = 0; j < cols; j++) {
var color = layout[i][j];
var color = normalizeColor(layout[i][j]);
var selectable = (color ? ' selectable' : '');
var roundedTL = (i === 0 && j === 0) || i === 0 && !layout[i][j - 1] ||
@@ -146,7 +256,7 @@ module.exports = {
grid += '<i ' +
'class="color-box ' + selectable + roundedTL + roundedTR + roundedBL +
roundedBR + '" ' +
'data-value="' + parseInt(color, 16) + '" ' +
(color ? 'data-value="' + parseInt(color, 16) + '" ' : '') +
'style="' +
'width:' + itemWidth + '%; ' +
'height:' + itemHeight + '%; ' +
@@ -155,13 +265,34 @@ module.exports = {
}
}
$elem.select('.color-box-container').add(HTML(grid));
// on very small layouts the boxes end up huge. The following adds extra padding
// to prevent them from being so big.
var extraPadding = 0;
if (cols === 3) {
extraPadding = 5;
}
if (cols === 2) {
extraPadding = 8;
}
var vPadding = (extraPadding * itemWidth / itemHeight) + '%';
var hPadding = extraPadding + '%';
$elem.select('.color-box-container')
.add(HTML(grid))
.set('$paddingTop', vPadding)
.set('$paddingRight', hPadding)
.set('$paddingBottom', vPadding)
.set('$paddingLeft', hPadding);
$elem.select('.color-box-wrap').set(
'$paddingBottom',
(itemWidth / itemHeight * 100) + '%'
);
var $valueDisplay = $elem.select('.value');
var $picker = $elem.select('.picker-wrap');
var disabled = self.$manipulatorTarget.get('disabled');
$elem.select('label').on('click', function(ev) {
$elem.select('label').on('click', function() {
if (!disabled) {
$picker.set('show'); // toggle visibility
}
@@ -191,5 +322,7 @@ module.exports = {
disabled = false;
});
// expose layout for testing
self._layout = layout;
}
};
+2 -1
View File
@@ -11,5 +11,6 @@ module.exports = {
toggle: require('./toggle'),
radiogroup: require('./radiogroup'),
checkboxgroup: require('./checkboxgroup'),
button: require('./button')
button: require('./button'),
slider: require('./slider')
};
+1 -1
View File
@@ -3,7 +3,7 @@
module.exports = {
name: 'input',
template: require('../../templates/components/input.tpl'),
style: require('../../styles/clay/components/input.scss'),
style: require('../../../tmp/input.css'),
manipulator: 'val',
defaults: {
label: '',
+1 -1
View File
@@ -3,7 +3,7 @@
module.exports = {
name: 'radiogroup',
template: require('../../templates/components/radiogroup.tpl'),
style: require('../../styles/clay/components/radiogroup.scss'),
style: require('../../../tmp/radiogroup.css'),
manipulator: 'radiogroup',
defaults: {
label: '',
+1 -1
View File
@@ -3,7 +3,7 @@
module.exports = {
name: 'select',
template: require('../../templates/components/select.tpl'),
style: require('../../styles/clay/components/select.scss'),
style: require('../../../tmp/select.css'),
manipulator: 'val',
defaults: {
label: '',
+50
View File
@@ -0,0 +1,50 @@
'use strict';
module.exports = {
name: 'slider',
template: require('../../templates/components/slider.tpl'),
style: require('../../../tmp/slider.css'),
manipulator: 'slider',
defaults: {
label: '',
description: '',
min: 0,
max: 100,
step: 1,
attributes: {}
},
initialize: function() {
var self = this;
var $value = self.$element.select('.value');
var $valuePad = self.$element.select('.value-pad');
var $slider = self.$manipulatorTarget;
/**
* Sets the value display
* @return {void}
*/
function setValueDisplay() {
var value = self.get().toFixed(self.precision);
$value.set('value', value);
$valuePad.set('innerHTML', value);
}
var step = $slider.get('step');
step = step.toString(10).split('.')[1];
self.precision = step ? step.length : 0;
self.on('change', setValueDisplay);
$slider.on('|input', setValueDisplay);
setValueDisplay();
$value.on('|input', function() {
$valuePad.set('innerHTML', this.get('value'));
});
$value.on('|change', function() {
self.set(this.get('value'));
setValueDisplay();
});
}
};
+1 -1
View File
@@ -3,7 +3,7 @@
module.exports = {
name: 'submit',
template: require('../../templates/components/submit.tpl'),
style: require('../../styles/clay/components/submit.scss'),
style: require('../../../tmp/submit.css'),
manipulator: 'button',
defaults: {
attributes: {}
+1 -1
View File
@@ -3,7 +3,7 @@
module.exports = {
name: 'toggle',
template: require('../../templates/components/toggle.tpl'),
style: require('../../styles/clay/components/toggle.scss'),
style: require('../../../tmp/toggle.css'),
manipulator: 'checked',
defaults: {
label: '',
+1 -1
View File
@@ -28,7 +28,7 @@ var clayConfig = new ClayConfig(settings, config, $mainForm, clayMeta);
$mainForm.on('submit', function() {
// Set the return URL depending on the runtime environment
location.href = returnTo +
encodeURIComponent(JSON.stringify(clayConfig.getSettings()));
encodeURIComponent(JSON.stringify(clayConfig.serialize()));
});
// Run the custom function in the context of the ClayConfig
+112 -40
View File
@@ -5,12 +5,13 @@
* @typedef {Object} Clay~ConfigItem
* @property {string} type
* @property {string|boolean|number} defaultValue
* @property {string} [appKey]
* @property {string} [messageKey]
* @property {string} [id]
* @property {string} [label]
* @property {Object} [attributes]
* @property {Array} [options]
* @property {Array} [items]
* @property {Array} [capabilities]
*/
var HTML = require('../vendor/minified').HTML;
@@ -33,10 +34,22 @@ function ClayConfig(settings, config, $rootContainer, meta) {
var self = this;
var _settings = _.copyObj(settings);
var _items = [];
var _itemsById = {};
var _itemsByAppKey = {};
var _isBuilt = false;
var _items;
var _itemsById;
var _itemsByMessageKey;
var _isBuilt;
/**
* Initialize the item arrays and objects
* @private
* @return {void}
*/
function _initializeItems() {
_items = [];
_itemsById = {};
_itemsByMessageKey = {};
_isBuilt = false;
}
/**
* Add item(s) to the config
@@ -49,34 +62,36 @@ function ClayConfig(settings, config, $rootContainer, meta) {
item.forEach(function(item) {
_addItems(item, $container);
});
} else if (item.type === 'section') {
var $wrapper = HTML('<div class="section">');
$container.add($wrapper);
_addItems(item.items, $wrapper);
} else {
var _item = _.copyObj(item);
_item.clayId = _items.length;
} else if (utils.includesCapability(meta.activeWatchInfo, item.capabilities)) {
if (item.type === 'section') {
var $wrapper = HTML('<div class="section">');
$container.add($wrapper);
_addItems(item.items, $wrapper);
} else {
var _item = _.copyObj(item);
_item.clayId = _items.length;
var clayItem = new ClayItem(_item).initialize(self);
var clayItem = new ClayItem(_item).initialize(self);
if (_item.id) {
_itemsById[_item.id] = clayItem;
if (_item.id) {
_itemsById[_item.id] = clayItem;
}
if (_item.messageKey) {
_itemsByMessageKey[_item.messageKey] = clayItem;
}
_items.push(clayItem);
// set the value of the item via the manipulator to ensure consistency
var value = typeof _settings[_item.messageKey] !== 'undefined' ?
_settings[_item.messageKey] :
_item.defaultValue;
clayItem.set(typeof value !== 'undefined' ? value : '');
$container.add(clayItem.$element);
}
if (_item.appKey) {
_itemsByAppKey[_item.appKey] = clayItem;
}
_items.push(clayItem);
// set the value of the item via the manipulator to ensure consistency
var value = typeof _settings[_item.appKey] !== 'undefined' ?
_settings[_item.appKey] :
(_item.defaultValue || '');
clayItem.set(value);
$container.add(clayItem.$element);
}
}
@@ -97,6 +112,7 @@ function ClayConfig(settings, config, $rootContainer, meta) {
}
self.meta = meta;
self.$rootContainer = $rootContainer;
self.EVENTS = {
/**
@@ -111,7 +127,21 @@ function ClayConfig(settings, config, $rootContainer, meta) {
* value set
* @const
*/
AFTER_BUILD: 'AFTER_BUILD'
AFTER_BUILD: 'AFTER_BUILD',
/**
* Called if .build() is executed after the page has already been built and
* before the existing content is destroyed
* @const
*/
BEFORE_DESTROY: 'BEFORE_DESTROY',
/**
* Called if .build() is executed after the page has already been built and after
* the existing content is destroyed
* @const
*/
AFTER_DESTROY: 'AFTER_DESTROY'
};
utils.updateProperties(self.EVENTS, {writable: false});
@@ -124,12 +154,12 @@ function ClayConfig(settings, config, $rootContainer, meta) {
};
/**
* @param {string} appKey
* @param {string} messageKey
* @returns {ClayItem}
*/
self.getItemByAppKey = function(appKey) {
_checkBuilt('getItemByAppKey');
return _itemsByAppKey[appKey];
self.getItemByMessageKey = function(messageKey) {
_checkBuilt('getItemByMessageKey');
return _itemsByMessageKey[messageKey];
};
/**
@@ -152,13 +182,33 @@ function ClayConfig(settings, config, $rootContainer, meta) {
});
};
/**
* @param {string} group
* @returns {Array.<ClayItem>}
*/
self.getItemsByGroup = function(group) {
_checkBuilt('getItemsByGroup');
return _items.filter(function(item) {
return item.config.group === group;
});
};
/**
* @returns {Object}
*/
self.getSettings = function() {
_checkBuilt('getSettings');
_.eachObj(_itemsByAppKey, function(appKey, item) {
_settings[appKey] = item.get();
self.serialize = function() {
_checkBuilt('serialize');
_settings = {};
_.eachObj(_itemsByMessageKey, function(messageKey, item) {
_settings[messageKey] = {
value: item.get()
};
if (item.precision) {
_settings[messageKey].precision = item.precision;
}
});
return _settings;
};
@@ -166,18 +216,40 @@ function ClayConfig(settings, config, $rootContainer, meta) {
// @todo maybe don't do this and force the static method
self.registerComponent = ClayConfig.registerComponent;
/**
* Empties the root container
* @returns {ClayConfig}
*/
self.destroy = function() {
var el = $rootContainer[0];
self.trigger(self.EVENTS.BEFORE_DESTROY);
while (el.firstChild) {
el.removeChild(el.firstChild);
}
_initializeItems();
self.trigger(self.EVENTS.AFTER_DESTROY);
return self;
};
/**
* Build the config page. This must be run before any of the get methods can be run
* If you call this method after the page has already been built, teh page will be
* destroyed and built again.
* @returns {ClayConfig}
*/
self.build = function() {
if (_isBuilt) {
self.destroy();
}
self.trigger(self.EVENTS.BEFORE_BUILD);
_addItems(config, $rootContainer);
_addItems(self.config, $rootContainer);
_isBuilt = true;
self.trigger(self.EVENTS.AFTER_BUILD);
return self;
};
_initializeItems();
// attach event methods
ClayEvents.call(self, $rootContainer);
+2 -2
View File
@@ -29,7 +29,7 @@ function ClayItem(config) {
self.id = config.id || null;
/** @type {string|null} */
self.appKey = config.appKey || null;
self.messageKey = config.messageKey || null;
/** @type {Object} */
self.config = config;
@@ -59,7 +59,7 @@ function ClayItem(config) {
};
// attach event methods
ClayEvents.call(self, self.$element);
ClayEvents.call(self, self.$manipulatorTarget);
// attach the manipulator methods to the clayItem
_.eachObj(_component.manipulator, function(methodName, method) {
+30 -16
View File
@@ -85,6 +85,21 @@ module.exports = {
hide: hide,
show: show
},
slider: {
get: function() {
return parseFloat(this.$manipulatorTarget.get('value'));
},
set: function(value) {
var initVal = this.get();
this.$manipulatorTarget.set('value', value);
if (this.get() === initVal) { return this; }
return this.trigger('change');
},
disable: disable,
enable: enable,
hide: hide,
show: show
},
checked: {
get: function() {
return this.$manipulatorTarget.get('checked');
@@ -118,24 +133,27 @@ module.exports = {
checkboxgroup: {
get: function() {
var result = [];
this.$element.select('input:checked').each(function(item) {
result.push(item.value);
this.$element.select('input').each(function(item) {
result.push(!!item.checked);
});
return result;
},
set: function(values) {
var self = this;
values = values || [];
values = Array.isArray(values) ? values : [];
while (values.length < this.get().length) {
values.push(false);
}
if (_.equals(this.get(), values)) { return this; }
self.$element.select('input').set('checked', false);
self.$element.select('input')
.set('checked', false)
.each(function(item, index) {
item.checked = !!values[index];
});
values.map(function(value) {
self.$element
.select('input[value="' + value.toString(10).replace('"', '\\"') + '"]')
.set('checked', true);
});
return self.trigger('change');
},
disable: disable,
@@ -145,16 +163,12 @@ module.exports = {
},
color: {
get: function() {
return parseInt(this.$manipulatorTarget.get('value'), 16);
return parseInt(this.$manipulatorTarget.get('value'), 10) || 0;
},
set: function(value) {
switch (typeof value) {
case 'number': value = value.toString(16); break;
case 'string': value = value.replace(/^#|^0x/, ''); break;
}
value = value || '000000';
value = this.roundColorToLayout(value || 0);
if (this.get() === parseInt(value, 16)) { return this; }
if (this.get() === value) { return this; }
this.$manipulatorTarget.set('value', value);
return this.trigger('change');
},
+115 -45
View File
@@ -18,53 +18,123 @@ module.exports.updateProperties = function(obj, descriptor) {
});
};
/**
* Converts the val into a type compatible with Pebble.sendAppMessage().
* - Strings will be returned without modification
* - Numbers will be returned without modification
* - Booleans will be converted to a 0 or 1
* - Arrays that contain strings will be split with a zero.
* eg: ['one', 'two'] becomes ['one', 0, 'two', 0]
* - Arrays that contain numbers will be returned without modification
* eg: [1, 2] becomes [1, 2]
* - Arrays that contain booleans will be converted to a 0 or 1
* eg: [true, false] becomes [1, 0]
* - Arrays must be single dimensional
* @param {number|string|boolean|Array} val
* @returns {number|string|Array}
*/
module.exports.prepareForAppMessage = function(val) {
var result;
module.exports.capabilityMap = {
PLATFORM_APLITE: {
platforms: ['aplite'],
minFwMajor: 0,
minFwMinor: 0
},
PLATFORM_BASALT: {
platforms: ['basalt'],
minFwMajor: 0,
minFwMinor: 0
},
PLATFORM_CHALK: {
platforms: ['chalk'],
minFwMajor: 0,
minFwMinor: 0
},
PLATFORM_DIORITE: {
platforms: ['diorite'],
minFwMajor: 0,
minFwMinor: 0
},
PLATFORM_EMERY: {
platforms: ['emery'],
minFwMajor: 0,
minFwMinor: 0
},
PLATFORM_FLINT: {
platforms: ['flint'],
minFwMajor: 0,
minFwMinor: 0
},
BW: {
platforms: ['aplite', 'diorite', 'flint'],
minFwMajor: 0,
minFwMinor: 0
},
COLOR: {
platforms: ['basalt', 'chalk', 'emery'],
minFwMajor: 0,
minFwMinor: 0
},
MICROPHONE: {
platforms: ['basalt', 'chalk', 'diorite', 'emery', 'flint'],
minFwMajor: 0,
minFwMinor: 0
},
SMARTSTRAP: {
platforms: ['basalt', 'chalk', 'diorite', 'emery'],
minFwMajor: 3,
minFwMinor: 4
},
SMARTSTRAP_POWER: {
platforms: ['basalt', 'chalk', 'emery'],
minFwMajor: 3,
minFwMinor: 4
},
HEALTH: {
platforms: ['basalt', 'chalk', 'diorite', 'emery', 'flint'],
minFwMajor: 3,
minFwMinor: 10
},
RECT: {
platforms: ['aplite', 'basalt', 'diorite', 'emery', 'flint'],
minFwMajor: 0,
minFwMinor: 0
},
ROUND: {
platforms: ['chalk'],
minFwMajor: 0,
minFwMinor: 0
},
DISPLAY_144x168: {
platforms: ['aplite', 'basalt', 'diorite', 'flint'],
minFwMajor: 0,
minFwMinor: 0
},
DISPLAY_180x180_ROUND: {
platforms: ['chalk'],
minFwMajor: 0,
minFwMinor: 0
},
DISPLAY_200x228: {
platforms: ['emery'],
minFwMajor: 0,
minFwMinor: 0
}
};
if (typeof val === 'boolean') {
result = val ? 1 : 0;
} else if (Array.isArray(val)) {
result = [];
val.forEach(function(item) {
var itemConverted = module.exports.prepareForAppMessage(item);
result.push(itemConverted);
if (typeof itemConverted === 'string') {
result.push(0);
}
});
} else {
result = val;
/**
* Checks if all of the provided capabilities are compatible with the watch
* @param {Object} activeWatchInfo
* @param {Array<string>} [capabilities]
* @return {boolean}
*/
module.exports.includesCapability = function(activeWatchInfo, capabilities) {
var notRegex = /^NOT_/;
var result = [];
if (!capabilities || !capabilities.length) {
return true;
}
return result;
};
for (var i = capabilities.length - 1; i >= 0; i--) {
var capability = capabilities[i];
var mapping = module.exports.capabilityMap[capability.replace(notRegex, '')];
/**
* Converts a Clay settings dict into one that is compatible with
* Pebble.sendAppMessage();
* @see {prepareForAppMessage}
* @param {Object} settings
* @returns {{}}
*/
module.exports.prepareSettingsForAppMessage = function(settings) {
var result = {};
Object.keys(settings).forEach(function(key) {
result[key] = module.exports.prepareForAppMessage(settings[key]);
});
return result;
if (!mapping ||
mapping.platforms.indexOf(activeWatchInfo.platform) === -1 ||
mapping.minFwMajor > activeWatchInfo.firmware.major ||
mapping.minFwMajor === activeWatchInfo.firmware.major &&
mapping.minFwMinor > activeWatchInfo.firmware.minor
) {
result.push(!!capability.match(notRegex));
} else {
result.push(!capability.match(notRegex));
}
}
return result.indexOf(false) === -1;
};
+7
View File
@@ -67,6 +67,12 @@ h6 {
@include font-size(0.8);
}
input {
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
label {
display: flex;
justify-content: space-between;
@@ -107,6 +113,7 @@ label {
.tap-highlight {
@include tap-highlight();
border-radius: $border-radius;
}
.component {
+2 -3
View File
@@ -11,8 +11,6 @@ $item-spacing-v: $base-height / 2;
$item-spacing-h: 0.75rem;
$small-component-width: $base-height * $golden;
// ---- Colors ------
$color-orange: #ff4700;
$color-orange-dark: #993d19;
@@ -37,4 +35,5 @@ $color-gray-11: #f2f2f2;
$button-padding: 0.6rem;
$button-padding-ios: 0.5rem;
$box-shadow-small-components: $color-gray-1 0 0.1rem 0.1rem;
$box-shadow-small-components: 0 0.1rem 0.1rem $color-gray-1;
$box-shadow-large-components: 0 rem(3px) rem(15px) rgba(0,0,0,0.4);
+15 -7
View File
@@ -12,6 +12,7 @@
border-radius: $base-height / 2;
box-shadow: $box-shadow-small-components;
display: block;
background: #000;
}
.picker-wrap {
@@ -20,17 +21,25 @@
right: 0;
bottom: 0;
position: fixed;
padding: 1rem;
background: rgba(0,0,0,0.7);
padding: $item-spacing-v $item-spacing-h /2;
background: rgba(0, 0, 0, 0.65);
opacity: 0;
transition: opacity 70ms ease-in 175ms;
transition: opacity 100ms ease-in 175ms;
pointer-events: none;
z-index: 100;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.picker {
padding: 1rem;
background: $color-gray-11;
padding: $item-spacing-v $item-spacing-h;
background: $color-gray-4;
box-shadow: $box-shadow-large-components;
border-radius: $border-radius;
width: 100%;
max-width: 26rem;
overflow: auto;
}
&.show {
@@ -46,7 +55,6 @@
height: 0;
width: 100%;
padding: 0 0 100% 0; // overridden with inline style
margin: 0.6em 0 0;
.color-box-container {
position: absolute;
@@ -77,7 +85,7 @@
}
&.selected {
transform: scale(1.15);
transform: scale(1.1);
border-radius: $border-radius;
box-shadow: #111 0 0 0.24rem;
position: relative;
+2
View File
@@ -31,6 +31,8 @@
vertical-align: baseline;
color: $color-white;
font-size: inherit;
appearance: none;
min-height: $item-spacing-v + ($base-height + 0rem) ;
@include placeholder {
color: $color-gray-8;
-1
View File
@@ -35,7 +35,6 @@
right: 0;
top: 0;
bottom: 0;
background: #000;
width: 100%;
border: none;
margin:0;
+127
View File
@@ -0,0 +1,127 @@
@import "clay";
.component-slider {
.section & {
padding: 0;
}
label {
display: block;
}
.label-container {
display: flex;
align-items: center;
width: 100%;
padding-bottom: $item-spacing-v;
}
.label {
flex: 1;
min-width: 1rem;
display: block;
padding-right: $item-spacing-h;
}
.value-wrap {
display: block;
position: relative;
}
.value,
.value-pad {
display: block;
background: $color-gray-2;
border-radius: $border-radius;
padding: $item-spacing-v / 2 $item-spacing-h / 2;
border: none;
vertical-align: baseline;
color: $color-white;
text-align: right;
margin: 0;
min-width: 1rem;
}
.value-pad {
visibility: hidden;
&:before {
content: ' ';
display: inline-block;
}
}
.value {
max-width: 100%;
position: absolute;
left:0;
top:0
}
.input-wrap {
padding: 0 $item-spacing-h $item-spacing-v;
}
.input {
$track-height: rem(3px);
display: block;
position: relative;
min-width: 100%;
height: $base-height;
overflow: hidden;
margin-left: 0;
&:before {
content: '';
display: block;
position: absolute;
height: $track-height;
background: $color-gray-6;
width: 100%;
top: $base-height / 2 - $track-height / 2;
}
.slider {
display: block;
width: 100%;
appearance: none;
position: relative;
height: $base-height;
margin: 0;
background-color: transparent;
&:focus {
outline: none;
}
&::-webkit-slider-runnable-track {
border: none;
height: $base-height;
width: 100%;
background-color: transparent;
}
&::-webkit-slider-thumb {
appearance: none;
position: relative;
height: $base-height;
width: $base-height;
background-color: $color-orange;
border-radius: 50%;
&:before {
content: "";
position: absolute;
left: -1000px;
top: $base-height / 2 - $track-height / 2;
height: $track-height;
width: 1001px;
background: $color-orange;
}
}
}
}
}
+2 -8
View File
@@ -3,14 +3,8 @@
<div class="checkbox-group">
{{each options}}
<label class="tap-highlight">
<span class="label">{{{this.label}}}</span>
<input
type="checkbox"
value="{{this.value}}"
name="clay-{{clayId}}"
data-manipulator-target
{{each key: attributes}}{{key}}="{{this}}"{{/each}}
/>
<span class="label">{{{this}}}</span>
<input type="checkbox" value="1" name="clay-{{clayId}}" />
<i></i>
</label>
{{/each}}
-1
View File
@@ -8,7 +8,6 @@
type="radio"
value="{{this.value}}"
name="clay-{{clayId}}"
data-manipulator-target
{{each key: attributes}}{{key}}="{{this}}"{{/each}}
/>
<i></i>
+9 -1
View File
@@ -4,7 +4,15 @@
<span class="value"></span>
<select data-manipulator-target {{each key: attributes}}{{key}}="{{this}}"{{/each}}>
{{each options}}
<option value="{{this.value}}" class="item-select-option">{{this.label}}</option>
{{if Array.isArray(this.value)}}
<optgroup label="{{this.label}}">
{{each this.value}}
<option value="{{this.value}}" class="item-select-option">{{this.label}}</option>
{{/each}}
</optgroup>
{{else}}
<option value="{{this.value}}" class="item-select-option">{{this.label}}</option>
{{/if}}
{{/each}}
</select>
</label>
+25
View File
@@ -0,0 +1,25 @@
<div class="component component-slider">
<label class="tap-highlight">
<span class="label-container">
<span class="label">{{{label}}}</span>
<span class="value-wrap">
<span class="value-pad"></span>
<input type="text" class="value" />
</span>
</span>
<span class="input">
<input
data-manipulator-target
class="slider"
type="range"
min="{{min}}"
max="{{max}}"
step="{{step}}"
{{each key: attributes}}{{key}}="{{this}}"{{/each}}
/>
</span>
</label>
{{if description}}
<div class="description">{{{description}}}</div>
{{/if}}
</div>
+72 -7
View File
@@ -11,12 +11,13 @@ var componentRegistry = require('../src/scripts/lib/component-registry');
var idCounter = 0;
/**
* @param {Object} [extra] - add/replace keys in the meta
* @returns {{accountToken: string, watchToken: string, activeWatchInfo: {platform:
* string, model: string, language: string, firmware: {major: number, minor:
* number, patch: number, suffix: string}}}}
* number, patch: number, suffix: string}}, userData: {}}}
*/
module.exports.meta = function() {
return {
module.exports.meta = function(extra) {
var result = {
accountToken: '0123456789abcdef0123456789abcdef',
watchToken: '0123456789abcdef0123456789abcdef',
activeWatchInfo: {
@@ -29,8 +30,15 @@ module.exports.meta = function() {
patch: 2,
suffix: ''
}
}
},
userData: {}
};
_.eachObj(extra || {}, function(key, val) {
result[key] = val;
});
return result;
};
/**
@@ -45,7 +53,7 @@ module.exports.configItem = function(config, autoRegister) {
var result = _.extend({}, {
label: config.type + '-label',
appKey: 'appKey-' + idCounter,
messageKey: 'messageKey-' + idCounter,
id: 'id-' + idCounter
}, config);
@@ -87,14 +95,15 @@ module.exports.config = function(types, autoRegister) {
* @param {boolean} [build=true] - run the build method on the result
* @param {boolean} [autoRegister=true]
* @param {Object} [settings] - settings to pass to constructor
* @param {Object} [meta] - override the meta
* @returns {ClayConfig}
*/
module.exports.clayConfig = function(types, build, autoRegister, settings) {
module.exports.clayConfig = function(types, build, autoRegister, settings, meta) {
var clayConfig = new ClayConfig(
settings || {},
module.exports.config(types, autoRegister),
$(HTML('<div>')),
module.exports.meta()
module.exports.meta(meta)
);
return build === false ? clayConfig : clayConfig.build();
};
@@ -106,6 +115,8 @@ module.exports.clayConfig = function(types, build, autoRegister, settings) {
* @param {Object} [options] - Additional options to pass to Clay
* @param {boolean} [options.autoHandleEvents] - If false, Clay will not
* automatically handle the 'showConfiguration' and 'webviewclosed' events
* @param {*} [options.userData={}] - Arbitrary data to pass to the config page. Will
* be available as `clayConfig.meta.userData`
* @param {boolean} [destroyLocalStorage=true]
* @return {Clay}
*/
@@ -115,3 +126,57 @@ module.exports.clay = function(config, customFn, options, destroyLocalStorage) {
}
return new Clay(config, customFn, options);
};
/**
* @param {Array} keys
* @return {Object}
*/
module.exports.messageKeys = function(keys) {
var counter = 10000;
var result = {};
keys.forEach(function(key) {
var matches = key.match(/(.+?)(?:\[(\d*)\])?$/);
var parsedKey = matches[1];
var length = parseInt(matches[2] || 1, 10);
result[parsedKey] = counter;
counter += length;
});
return result;
};
/**
* @param {Object} obj
* @returns {Array}
*/
module.exports.messageKeysObjToArray = function(obj) {
return Object.keys(obj).map(function(key) {
return Array.isArray(obj[key]) ?
key + '[' + obj[key].length + ']' :
key;
});
};
/**
* @param {Object} expected
* @return {Object}
*/
module.exports.messageKeysExpected = function(expected) {
var result = {};
var messageKeys = module.exports.messageKeysObjToArray(expected);
messageKeys = module.exports.messageKeys(messageKeys);
Object.keys(expected).forEach(function(key) {
var expectedVal = Array.isArray(expected[key]) ? expected[key] : [expected[key]];
expectedVal.forEach(function(val, index) {
if (typeof val !== 'undefined') {
result[messageKeys[key] + index] = val;
}
});
});
return result;
};
+13 -16
View File
@@ -12,30 +12,22 @@ module.exports = function(config) {
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['browserify', 'source-map-support', 'mocha'],
client: {
mocha: {
timeout: 10000 // 10 sec because Travis struggles sometimes.
}
},
browserify: {
debug: true,
transform: [
[
'stringify',
{
extensions: ['.html', '.tpl']
extensions: ['.html', '.tpl', '.css']
}
],
'deamdify',
[
'sassify',
{
base64Encode: false,
sourceMap: false,
sourceMapEmbed: false,
sourceMapContents: false,
outputStyle: 'compact',
includePaths: [].concat(
require('bourbon').includePaths,
'src/styles'
)
}
],
[
'browserify-istanbul',
{
@@ -47,7 +39,12 @@ module.exports = function(config) {
]
}
]
]
],
configure: function(bundle) {
bundle.on('prebundle', function() {
bundle.ignore('message_keys');
});
}
},
// list of files / patterns to load in the browser
+3
View File
@@ -1,5 +1,8 @@
{
"env": {
"mocha": true
},
"rules": {
"max-params": [0]
}
}
+394
View File
@@ -3,6 +3,48 @@
var assert = require('chai').assert;
var fixture = require('../../fixture');
var sunlightColorMap = {
'000000': '000000', '000055': '001e41', '0000aa': '004387', '0000ff': '0068ca',
'005500': '2b4a2c', '005555': '27514f', '0055aa': '16638d', '0055ff': '007dce',
'00aa00': '5e9860', '00aa55': '5c9b72', '00aaaa': '57a5a2', '00aaff': '4cb4db',
'00ff00': '8ee391', '00ff55': '8ee69e', '00ffaa': '8aebc0', '00ffff': '84f5f1',
'550000': '4a161b', '550055': '482748', '5500aa': '40488a', '5500ff': '2f6bcc',
'555500': '564e36', '555555': '545454', '5555aa': '4f6790', '5555ff': '4180d0',
'55aa00': '759a64', '55aa55': '759d76', '55aaaa': '71a6a4', '55aaff': '69b5dd',
'55ff00': '9ee594', '55ff55': '9de7a0', '55ffaa': '9becc2', '55ffff': '95f6f2',
'aa0000': '99353f', 'aa0055': '983e5a', 'aa00aa': '955694', 'aa00ff': '8f74d2',
'aa5500': '9d5b4d', 'aa5555': '9d6064', 'aa55aa': '9a7099', 'aa55ff': '9587d5',
'aaaa00': 'afa072', 'aaaa55': 'aea382', 'aaaaaa': 'ababab', 'ffffff': 'ffffff',
'aaaaff': 'a7bae2', 'aaff00': 'c9e89d', 'aaff55': 'c9eaa7', 'aaffaa': 'c7f0c8',
'aaffff': 'c3f9f7', 'ff0000': 'e35462', 'ff0055': 'e25874', 'ff00aa': 'e16aa3',
'ff00ff': 'de83dc', 'ff5500': 'e66e6b', 'ff5555': 'e6727c', 'ff55aa': 'e37fa7',
'ff55ff': 'e194df', 'ffaa00': 'f1aa86', 'ffaa55': 'f1ad93', 'ffaaaa': 'efb5b8',
'ffaaff': 'ecc3eb', 'ffff00': 'ffeeab', 'ffff55': 'fff1b5', 'ffffaa': 'fff6d3'
};
/* eslint-disable comma-spacing, no-multi-spaces, max-len,
standard/array-bracket-even-spacing */
var standardLayouts = {
COLOR: [
[false , false , '55ff00', 'aaff55', false , 'ffff55', 'ffffaa', false , false ],
[false , 'aaffaa', '55ff55', '00ff00', 'aaff00', 'ffff00', 'ffaa55', 'ffaaaa', false ],
['55ffaa', '00ff55', '00aa00', '55aa00', 'aaaa55', 'aaaa00', 'ffaa00', 'ff5500', 'ff5555'],
['aaffff', '00ffaa', '00aa55', '55aa55', '005500', '555500', 'aa5500', 'ff0000', 'ff0055'],
[false , '55aaaa', '00aaaa', '005555', 'ffffff', '000000', 'aa5555', 'aa0000', false ],
['55ffff', '00ffff', '00aaff', '0055aa', 'aaaaaa', '555555', '550000', 'aa0055', 'ff55aa'],
['55aaff', '0055ff', '0000ff', '0000aa', '000055', '550055', 'aa00aa', 'ff00aa', 'ffaaff'],
[false , '5555aa', '5555ff', '5500ff', '5500aa', 'aa00ff', 'ff00ff', 'ff55ff', false ],
[false , false , false , 'aaaaff', 'aa55ff', 'aa55aa', false , false , false ]
],
GRAY: [
['000000', 'aaaaaa', 'ffffff']
],
BLACK_WHITE: [
['000000', 'ffffff']
]
};
/* eslint-enable */
/**
* @param {ClayItem} colorItem
* @returns {void}
@@ -11,6 +53,182 @@ function openPicker(colorItem) {
colorItem.$element.select('label')[0].click();
}
/**
* @param {string} hex
* @returns {string} - eg: "rgb(1, 2, 3)"
*/
function hexToRgb(hex) {
var result = /^(?:#|0x)?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ?
'rgb(' +
parseInt(result[1], 16) + ', ' +
parseInt(result[2], 16) + ', ' +
parseInt(result[3], 16) + ')' :
hex;
}
/**
* @param {number|string} value
* @returns {string}
*/
function normalizeColor(value) {
switch (typeof value) {
case 'number': value = value.toString(16); break;
case 'string': value = value.replace(/^#|^0x/, ''); break;
}
return value || '000000';
}
/**
* @param {Array.<Array>} layout
* @returns {Array}
*/
function flattenLayout(layout) {
return layout.reduce(function(a, b) {
return a.concat(b);
}, []);
}
/**
* @param {Array|string} [layout=color]
* @param {Array} [expectedColors]
* @return {void}
*/
function testCustomLayout(layout, expectedColors) {
describe('layout: ' + JSON.stringify(layout || 'default'), function() {
describe('Normal', function() {
testColors(false, layout, expectedColors);
});
describe('Sunlight', function() {
testColors(true, layout, expectedColors);
});
});
}
/**
* @param {string} platform
* @param {Array} layout
* @param {boolean} allowGray
* @param {string} desc
* @param {Object} activeWatchInfo
* @return {void}
*/
function testAutoLayout(platform, layout, allowGray, desc, activeWatchInfo) {
it('chooses the best layout for the ' + (desc || platform) +
' platform when allowGray is ' + allowGray,
function() {
var clayConfig = fixture.clayConfig(
[{type: 'color', allowGray: allowGray}],
true,
true,
{},
{
activeWatchInfo: activeWatchInfo
}
);
var colorItem = clayConfig.getItemsByType('color')[0];
assert.deepEqual(colorItem._layout, layout);
});
}
/**
* @param {number|string} input
* @param {number} expected
* @param {array|string} layout
* @return {void}
*/
function testClosestToLayout(input, expected, layout) {
it('rounds ' + input + ' to ' + expected.toString(16) +
' in layout: ' + JSON.stringify(layout),
function() {
var clayConfig;
var colorItem;
clayConfig = fixture.clayConfig([{
type: 'color',
sunlight: false,
layout: layout
}]);
colorItem = clayConfig.getItemsByType('color')[0];
colorItem.set(input);
assert.strictEqual(colorItem.get(), expected);
clayConfig = fixture.clayConfig([{
type: 'color',
sunlight: false,
layout: layout
}]);
colorItem = clayConfig.getItemsByType('color')[0];
colorItem.set(input);
assert.strictEqual(colorItem.get(), expected);
});
}
/**
* @param {boolean} sunlight
* @param {Array|string} [layout=color]
* @param {Array} [expectedColors]
* @return {void}
*/
function testColors(sunlight, layout, expectedColors) {
var clayConfig;
var colorItem;
var $colorBoxes;
var colors;
beforeEach(function() {
clayConfig = fixture.clayConfig([
{
type: 'color',
layout: layout,
sunlight: sunlight
}
]);
colorItem = clayConfig.getItemsByType('color')[0];
layout = layout || 'COLOR';
if (typeof layout === 'string') {
layout = standardLayouts[layout];
}
colors = expectedColors || flattenLayout(layout);
$colorBoxes = colorItem.$element.select('.color-box');
});
it('Has the correct items in the layout', function() {
colors.forEach(function(color, index) {
if (color === false) {
assert.strictEqual($colorBoxes[index].dataset.value, undefined);
} else {
assert.strictEqual(
parseInt($colorBoxes[index].dataset.value, 10),
parseInt(color || 0, 16)
);
}
});
});
it('sets the color correctly', function() {
colors.forEach(function(color, index) {
if (color === false) {
assert.strictEqual(
$colorBoxes[index].style.backgroundColor,
'transparent'
);
} else {
var normalizedColor = normalizeColor(color);
assert.strictEqual(
$colorBoxes[index].style.backgroundColor,
hexToRgb(sunlight ? sunlightColorMap[normalizedColor] : normalizedColor)
);
}
});
});
}
describe('component - color', function() {
it('shows the sunlit colors in the value display', function() {
var clayConfig = fixture.clayConfig([
@@ -105,4 +323,180 @@ describe('component - color', function() {
$picker[0].click();
assert.strictEqual($picker.get('classList').contains('show'), false);
});
describe('color rounding', function() {
var layout = 'GRAY';
testClosestToLayout('000000', 0x000000, layout);
testClosestToLayout('005500', 0x000000, layout);
testClosestToLayout('55aa55', 0xaaaaaa, layout);
testClosestToLayout('ffaaff', 0xaaaaaa, layout);
testClosestToLayout('ffffaa', 0xffffff, layout);
testClosestToLayout('ffffff', 0xffffff, layout);
layout = 'BLACK_WHITE';
testClosestToLayout('000000', 0x000000, layout);
testClosestToLayout('005500', 0x000000, layout);
testClosestToLayout('55aa55', 0xffffff, layout);
testClosestToLayout('ffaaff', 0xffffff, layout);
testClosestToLayout('ffffaa', 0xffffff, layout);
testClosestToLayout('ffffff', 0xffffff, layout);
layout = 'COLOR';
testClosestToLayout('000000', 0x000000, layout);
testClosestToLayout('005500', 0x005500, layout);
testClosestToLayout('55bb55', 0x55aa55, layout);
testClosestToLayout('ff99ff', 0xffaaff, layout);
testClosestToLayout('ffffee', 0xffffff, layout);
testClosestToLayout('ffffff', 0xffffff, layout);
layout = ['ff0000', 'ff5500', 'ff00ff', '00ff00', '555555'];
testClosestToLayout('000000', 0x555555, layout);
testClosestToLayout('ff0000', 0xff0000, layout);
testClosestToLayout('ff1111', 0xff0000, layout);
testClosestToLayout('00aa00', 0x00ff00, layout);
testClosestToLayout('ff55ff', 0xff00ff, layout);
testClosestToLayout('aaaaaa', 0x555555, layout);
testClosestToLayout('ffffff', 0x555555, layout);
});
describe('layouts', function() {
// allow gray does nothing for 2.x
testAutoLayout('aplite', standardLayouts.BLACK_WHITE, true, 'aplite (2.x)',
null
);
testAutoLayout('aplite', standardLayouts.BLACK_WHITE, true, 'aplite (2.x)', {
platform: 'aplite',
model: 'qemu_platform_aplite',
language: 'en_US',
firmware: {
major: 2,
minor: 10,
patch: 0,
suffix: ''
}
});
testAutoLayout('aplite', standardLayouts.BLACK_WHITE, false, 'aplite (2.x)',
null
);
testAutoLayout('aplite', standardLayouts.BLACK_WHITE, false, 'aplite (2.x)', {
platform: 'aplite',
model: 'qemu_platform_aplite',
language: 'en_US',
firmware: {
major: 2,
minor: 10,
patch: 0,
suffix: ''
}
});
testAutoLayout('aplite', standardLayouts.BLACK_WHITE, false, 'aplite (3.x)', {
platform: 'aplite',
model: 'qemu_platform_aplite',
language: 'en_US',
firmware: {
major: 3,
minor: 10,
patch: 0,
suffix: ''
}
});
testAutoLayout('aplite', standardLayouts.GRAY, true, 'aplite (3.x)', {
platform: 'aplite',
model: 'qemu_platform_aplite',
language: 'en_US',
firmware: {
major: 3,
minor: 10,
patch: 0,
suffix: ''
}
});
testAutoLayout('basalt', standardLayouts.COLOR, true, '', {
platform: 'basalt',
model: 'qemu_platform_basalt',
language: 'en_US',
firmware: {
major: 3,
minor: 10,
patch: 0,
suffix: ''
}
});
testAutoLayout('chalk', standardLayouts.COLOR, true, '', {
platform: 'chalk',
model: 'qemu_platform_chalk',
language: 'en_US',
firmware: {
major: 3,
minor: 10,
patch: 0,
suffix: ''
}
});
testAutoLayout('diorite', standardLayouts.BLACK_WHITE, false, '', {
platform: 'diorite',
model: 'qemu_platform_diorite',
language: 'en_US',
firmware: {
major: 4,
minor: 0,
patch: 0,
suffix: ''
}
});
testAutoLayout('diorite', standardLayouts.GRAY, true, '', {
platform: 'diorite',
model: 'qemu_platform_diorite',
language: 'en_US',
firmware: {
major: 4,
minor: 0,
patch: 0,
suffix: ''
}
});
testCustomLayout();
testCustomLayout('COLOR');
testCustomLayout('GRAY');
testCustomLayout('BLACK_WHITE');
testCustomLayout(['aaffaa', '55ff55', '00ff00']);
testCustomLayout([false, '55ff55', '00ff00']);
testCustomLayout([['aaffaa', '55ff55', '00ff00']]);
testCustomLayout(
[['AAFFAA', '#55ff55', '0x00ff00', 0xff5500, null]],
['aaffaa', '55ff55', '00ff00', 'ff5500', false]
);
testCustomLayout([
['aaffaa', '55ff55', '00ff00'],
['005555', 'ffffff', '000000']]
);
testCustomLayout([
['aaffaa', '55ff55', '00ff00'],
[false, 'ffffff', false]]
);
testCustomLayout(
[
['aaffaa', '55ff55', '00ff00'],
[false, 'ffffff']
],
['aaffaa', '55ff55', '00ff00', false, 'ffffff', false]
);
testCustomLayout(
[
['aaffaa', '55ff55', false],
[false, 'ffffff']
],
['aaffaa', '55ff55', false, false, 'ffffff', false]
);
testCustomLayout(
[
['aaffaa', '55ff55'],
[false, 'ffffff', '000000']
],
['aaffaa', '55ff55', false, false, 'ffffff', '000000']
);
});
});
+15
View File
@@ -7,6 +7,7 @@ var HTML = require('../../../src/scripts/vendor/minified').HTML;
var components = require('../../../src/scripts/components');
var manipulators = require('../../../src/scripts/lib/manipulators');
var fixture = require('../../fixture');
var sinon = require('sinon');
var componentSchema = Joi.object().keys({
name: Joi.string().required(),
@@ -39,6 +40,20 @@ describe('components', function() {
it('is able to be passed to ClayConfig', function() {
fixture.clayConfig([component.name]);
});
it('only has one $manipulatorTarget', function() {
var configItem = fixture.clayConfig([component.name]).getAllItems()[0];
assert.strictEqual(configItem.$manipulatorTarget.length, 1);
});
it('only dispatches change events once', function() {
var configItem = fixture.clayConfig([component.name]).getAllItems()[0];
var handler = sinon.spy();
configItem.on('change', handler);
configItem.trigger('change');
assert.strictEqual(handler.callCount, 1);
});
});
});
});
+22
View File
@@ -21,4 +21,26 @@ describe('component - select', function() {
selectItem.set('value-2');
assert.strictEqual($valueDisplay.get('innerHTML'), 'label 2');
});
it('sets the value display to the correct value on change when using optgroups',
function() {
var clayConfig = fixture.clayConfig([
{
type: 'select',
defaultValue: 'value-1',
options: [
{ label: 'label 1', value: 'value-1' },
{ label: 'group', value: [
{ label: 'label 2', value: 'value-2' },
{ label: 'label 3', value: 'value-3' }
]}
]
}
]);
var selectItem = clayConfig.getItemsByType('select')[0];
var $valueDisplay = selectItem.$element.select('.value');
assert.strictEqual($valueDisplay.get('innerHTML'), 'label 1');
selectItem.set('value-2');
assert.strictEqual($valueDisplay.get('innerHTML'), 'label 2');
});
});
+95
View File
@@ -0,0 +1,95 @@
'use strict';
var assert = require('chai').assert;
var fixture = require('../../fixture');
describe('component - slider', function() {
it('sets the value display to the correct value on set', function() {
var clayConfig = fixture.clayConfig(['slider']);
var sliderItem = clayConfig.getItemsByType('slider')[0];
var $valueDisplay = sliderItem.$element.select('.value');
var $valueDisplayPad = sliderItem.$element.select('.value-pad');
assert.strictEqual($valueDisplay.get('value'), '50');
assert.strictEqual($valueDisplayPad.get('innerHTML'), '50');
sliderItem.set(75);
assert.strictEqual($valueDisplay.get('value'), '75');
assert.strictEqual($valueDisplayPad.get('innerHTML'), '75');
});
it('sets the value display to the correct value on slider change', function() {
var clayConfig = fixture.clayConfig(['slider']);
var sliderItem = clayConfig.getItemsByType('slider')[0];
var $valueDisplay = sliderItem.$element.select('.value');
var $valueDisplayPad = sliderItem.$element.select('.value-pad');
var $slider = sliderItem.$element.select('.slider');
assert.strictEqual($valueDisplay.get('value'), '50');
assert.strictEqual($valueDisplayPad.get('innerHTML'), '50');
$slider.set('value', 75).trigger('change');
assert.strictEqual($valueDisplay.get('value'), '75');
assert.strictEqual($valueDisplayPad.get('innerHTML'), '75');
});
it('sets the value-pad to the same value as what the user is typing', function() {
var clayConfig = fixture.clayConfig(['slider']);
var sliderItem = clayConfig.getItemsByType('slider')[0];
var $valueDisplay = sliderItem.$element.select('.value');
var $valueDisplayPad = sliderItem.$element.select('.value-pad');
assert.strictEqual($valueDisplay.get('value'), '50');
assert.strictEqual($valueDisplayPad.get('innerHTML'), '50');
$valueDisplay.set('value', 75).trigger('input');
assert.strictEqual($valueDisplay.get('value'), '75');
assert.strictEqual($valueDisplayPad.get('innerHTML'), '75');
});
it('sets the slider to the correct value on user input', function() {
var clayConfig = fixture.clayConfig(['slider']);
var sliderItem = clayConfig.getItemsByType('slider')[0];
var $valueDisplay = sliderItem.$element.select('.value');
var $slider = sliderItem.$element.select('.slider');
assert.strictEqual($valueDisplay.get('value'), '50');
assert.strictEqual($slider.get('value'), '50');
$valueDisplay.set('value', 75).trigger('change');
assert.strictEqual($slider.get('value'), '75');
});
it('sets the precision correctly', function() {
var clayConfig = fixture.clayConfig([{
type: 'slider',
step: 0.25
}]);
var sliderItem = clayConfig.getItemsByType('slider')[0];
assert.strictEqual(sliderItem.precision, 2);
});
it('always displays decimal when precision is less than 1', function() {
var clayConfig = fixture.clayConfig([{
type: 'slider',
step: 0.05
}]);
var sliderItem = clayConfig.getItemsByType('slider')[0];
var $valueDisplay = sliderItem.$element.select('.value');
var $valueDisplayPad = sliderItem.$element.select('.value-pad');
var $slider = sliderItem.$element.select('.slider');
assert.strictEqual($valueDisplay.get('value'), '50.00');
assert.strictEqual($valueDisplayPad.get('innerHTML'), '50.00');
assert.strictEqual($slider.get('value'), '50');
sliderItem.set(75);
assert.strictEqual($valueDisplay.get('value'), '75.00');
assert.strictEqual($valueDisplayPad.get('innerHTML'), '75.00');
assert.strictEqual($slider.get('value'), '75');
sliderItem.set(33.25);
assert.strictEqual($valueDisplay.get('value'), '33.25');
assert.strictEqual($valueDisplayPad.get('innerHTML'), '33.25');
assert.strictEqual($slider.get('value'), '33.25');
sliderItem.set(17.1);
assert.strictEqual($valueDisplay.get('value'), '17.10');
assert.strictEqual($valueDisplayPad.get('innerHTML'), '17.10');
assert.strictEqual($slider.get('value'), '17.1');
});
});
+329 -89
View File
@@ -7,34 +7,40 @@ var standardComponents = require('../../src/scripts/components');
var sinon = require('sinon');
var toSource = require('tosource');
var accountToken = '0123456789abcdef0123456789abcdef';
var watchToken = '0123456789abcdef0123456789abcdef';
var activeWatchInfo = {
platform: 'chalk',
model: 'qemu_platform_chalk',
language: 'en_US',
firmware: {
major: 3,
minor: 3,
patch: 2,
suffix: ''
}
};
/**
* @return {void}
*/
function stubPebble() {
var meta = fixture.meta();
global.Pebble = {
addEventListener: sinon.stub(),
openURL: sinon.stub(),
sendAppMessage: sinon.stub(),
getActiveWatchInfo: sinon.stub().returns(activeWatchInfo),
getAccountToken: sinon.stub().returns(accountToken),
getWatchToken: sinon.stub().returns(watchToken)
getActiveWatchInfo: sinon.stub().returns(meta.activeWatchInfo),
getAccountToken: sinon.stub().returns(meta.accountToken),
getWatchToken: sinon.stub().returns(meta.watchToken)
};
}
/**
* @param {Array} keys
* @return {Object}
*/
function stubMessageKeys(keys) {
var messageKeys = require('message_keys');
Object.keys(messageKeys).forEach(function(key) {
delete messageKeys[key];
});
var newKeys = fixture.messageKeys(keys);
Object.keys(newKeys).forEach(function(key) {
messageKeys[key] = newKeys[key];
});
return messageKeys;
}
describe('Clay', function() {
describe('Clay constructor', function() {
it('throws if the config is not an array', function() {
@@ -43,6 +49,15 @@ describe('Clay', function() {
}, /must be an Array/i);
});
it('throws if the config contains appKeys', function() {
assert.throws(function() {
fixture.clay([
{type: 'input', appKey: 'foo'},
{type: 'someCustomComponent', appKey: 'bar'}
]);
}, /appKeys are no longer supported/i);
});
it('throws if customFn is not a function', function() {
assert.throws(function() {
fixture.clay([], {});
@@ -74,8 +89,6 @@ describe('Clay', function() {
it('handles the "showConfiguration" event if autoHandleEvents is not false',
function() {
this.timeout(10000); // 10 seconds
stubPebble();
var clay = fixture.clay([]);
@@ -94,12 +107,16 @@ describe('Clay', function() {
stubPebble();
fixture.clay([]);
var logStub = sinon.stub(console, 'log');
var expected = {someSetting: 'value'};
stubMessageKeys(fixture.messageKeysObjToArray(expected));
Pebble.addEventListener
.withArgs('webviewclosed')
.callArgWith(1, { response: '%7B%22appKey%22%3A%22value%22%7D' });
.callArgWith(1, {response: encodeURIComponent(JSON.stringify(expected))});
var expectedAppMessage = fixture.messageKeysExpected(expected);
assert(Pebble.addEventListener.calledWith('webviewclosed'));
assert(Pebble.sendAppMessage.calledWith({ appKey: 'value' }));
assert(Pebble.sendAppMessage.calledWith(expectedAppMessage));
Pebble.sendAppMessage.callArg(1);
assert(logStub.calledWith('Sent config data to Pebble'));
@@ -135,6 +152,23 @@ describe('Clay', function() {
});
});
describe('.version', function() {
it('has the correct version', function() {
var config = fixture.config(['input', 'text', 'color']);
var clay = fixture.clay(config);
assert.strictEqual(clay.version, require('../../package.json').version);
});
});
describe('.config', function() {
it('is a copy not a reference', function() {
var config = fixture.config(['input', 'text', 'color']);
var clay = fixture.clay(config);
assert.notStrictEqual(clay.config, config);
assert.deepEqual(clay.config, config);
});
});
describe('.registerComponent()', function() {
it('adds the component to the this.components', function() {
var clay = fixture.clay([]);
@@ -156,13 +190,13 @@ describe('Clay', function() {
* @returns {string}
*/
function decodeUrl(url) {
return atob(decodeURIComponent(url.replace(/^.*?[#,]/, '')));
return decodeURIComponent(url.replace(/^.*?[#,]/, ''));
}
describe('string substitutions', function() {
var customFn = function() { this.getAllItems(); };
var config = fixture.config(['input', 'color']);
var settings = { appKey: 'value' };
var settings = { messageKey: 'value' };
var clay;
var html;
@@ -235,24 +269,60 @@ describe('Clay', function() {
});
});
describe('.getSettings()', function() {
it('stores the response to localStorage and returns the decoded data',
describe('.getSettings', function() {
it('it writes to localStorage and returns the data when input is encoded',
function() {
var clay = fixture.clay([]);
var result = clay.getSettings('%7B%22appKey%22%3A%22value%22%7D');
assert.equal(localStorage.getItem('clay-settings'), '{"appKey":"value"}');
assert.deepEqual(result, {appKey: 'value'});
var settings = encodeURIComponent(JSON.stringify({
key1: 'value1',
key2: {value: 'value2'}
}));
var expected = {
key1: 'value1',
key2: 'value2'
};
stubMessageKeys(fixture.messageKeysObjToArray(expected));
var result = clay.getSettings(settings);
assert.equal(
localStorage.getItem('clay-settings'),
JSON.stringify(expected)
);
assert.deepEqual(result, fixture.messageKeysExpected(expected));
});
it('it writes to localStorage and returns the data when input is not encoded',
function() {
var clay = fixture.clay([]);
var settings = JSON.stringify({
key1: 'value1',
key2: {value: 'value2%7Dbreaks'}
});
var expected = {
key1: 'value1',
key2: 'value2%7Dbreaks'
};
stubMessageKeys(fixture.messageKeysObjToArray(expected));
var result = clay.getSettings(settings);
assert.equal(
localStorage.getItem('clay-settings'),
JSON.stringify(expected)
);
assert.deepEqual(result, fixture.messageKeysExpected(expected));
});
it('does not store the response if it is invalid JSON and logs an error',
function() {
var clay = fixture.clay([]);
localStorage.setItem('clay-settings', '{"appKey":"value"}');
localStorage.setItem('clay-settings', '{"messageKey":"value"}');
assert.throws(function() {
clay.getSettings('not valid JSON');
}, /Not Valid JSON/i);
assert.equal(localStorage.getItem('clay-settings'), '{"appKey":"value"}');
assert.equal(localStorage.getItem('clay-settings'), '{"messageKey":"value"}');
});
it('Prepares the settings for sendAppMessage', function() {
@@ -264,19 +334,34 @@ describe('Clay', function() {
test4: ['cb-1', 'cb-3'],
test5: 12345,
test6: [1, 2, 3, 4],
test7: [true, false, true]
test7: [true, false, true],
test8: {
precision: 2,
value: 12.34
},
test9: {
precision: 1,
value: [1, 2, 3, 4]
}
}));
var expected = {
test1: 0,
test2: 'val-2',
test3: 1,
test4: ['cb-1', 0, 'cb-3', 0],
test4: ['cb-1', 'cb-3'],
test5: 12345,
test6: [1, 2, 3, 4],
test7: [1, 0, 1]
test7: [1, 0, 1],
test8: 1234,
test9: [10, 20, 30, 40]
};
assert.deepEqual(clay.getSettings(response), expected);
stubMessageKeys(fixture.messageKeysObjToArray(expected));
assert.deepEqual(
clay.getSettings(response),
fixture.messageKeysExpected(expected)
);
});
it('does not prepare the settings for sendAppMessage if convert is false',
@@ -289,7 +374,11 @@ describe('Clay', function() {
test4: ['cb-1', 'cb-3'],
test5: 12345,
test6: [1, 2, 3, 4],
test7: [true, false, true]
test7: [true, false, true],
test8: {
precision: 2,
value: 12.34
}
};
var response = encodeURIComponent(JSON.stringify(settings));
@@ -297,41 +386,86 @@ describe('Clay', function() {
});
});
describe('.setSettings', function() {
it('it writes to localStorage when passing an object',
function() {
var clay = fixture.clay([]);
var settings = {
key1: 'value1',
key2: 'value2'
};
var expected = {
key1: 'value1',
key2: 'value2'
};
clay.setSettings(settings);
assert.equal(
localStorage.getItem('clay-settings'),
JSON.stringify(expected)
);
});
it('it writes to localStorage when passing a key and a value',
function() {
var clay = fixture.clay([]);
var expected = {
key1: 'value1',
key2: 'value2%7Dbreaks'
};
clay.setSettings('key1', 'value1');
clay.setSettings('key2', 'value2%7Dbreaks');
assert.equal(localStorage.getItem('clay-settings'),
JSON.stringify(expected)
);
});
it('doesn\'t throw and logs an error if settings in localStorage are broken',
function() {
var clay = fixture.clay([]);
var errorStub = sinon.stub(console, 'error');
localStorage.setItem('clay-settings', 'not valid JSON');
assert.doesNotThrow(function() {
clay.setSettings('key', 'value');
});
assert(errorStub.calledWithMatch(/SyntaxError/i));
errorStub.restore();
});
});
describe('.meta', function() {
var emptyMeta = {
activeWatchInfo: null,
accountToken: '',
watchToken: ''
watchToken: '',
userData: {}
};
it('populates the meta in the showConfiguration handler', function() {
stubPebble();
var clay = fixture.clay([]);
var userData = {foo: 'bar'};
var clay = fixture.clay([], null, {userData: userData});
// meta only gets populated after showConfiguration happens
assert.deepEqual(clay.meta, emptyMeta);
Pebble.addEventListener.withArgs('showConfiguration').callArg(1);
assert.deepEqual(clay.meta, {
activeWatchInfo: activeWatchInfo,
accountToken: accountToken,
watchToken: watchToken
});
assert.deepEqual(clay.meta, fixture.meta({userData: userData}));
});
it('populates the meta in the ready handler', function() {
stubPebble();
var clay = fixture.clay([], null, {autoHandleEvents: false});
var userData = {foo: 'bar'};
var clay = fixture.clay([], null, {
autoHandleEvents: false,
userData: userData
});
// meta only gets populated after ready happens
assert.deepEqual(clay.meta, emptyMeta);
Pebble.addEventListener.withArgs('ready').callArg(1);
assert.deepEqual(clay.meta, {
activeWatchInfo: activeWatchInfo,
accountToken: accountToken,
watchToken: watchToken
});
assert.deepEqual(clay.meta, fixture.meta({userData: userData}));
});
it('populates the meta with with empty values when there is no Pebble global',
@@ -344,49 +478,155 @@ describe('Clay', function() {
});
describe('Clay.encodeDataUri()', function() {
/**
* @return {void}
*/
function testEncodeDataUri() {
it('adds the correct prefix', function() {
assert.equal(Clay.encodeDataUri('test', 'prefix:'), 'prefix:dGVzdA%3D%3D');
assert.equal(
Clay.encodeDataUri('test'),
'data:text/html;base64,dGVzdA%3D%3D'
);
});
it('encodes the data correctly', function() {
assert.equal(Clay.encodeDataUri('test', ''), 'dGVzdA%3D%3D');
assert.equal(Clay.encodeDataUri('test{2}', ''), 'dGVzdHsyfQ%3D%3D');
assert.equal(Clay.encodeDataUri('test{10}', ''), 'dGVzdHsxMH0%3D');
});
it('throws if the input is invalid', function() {
assert.throws(function() {
Clay.encodeDataUri('♥');
});
});
}
describe('native', function() {
testEncodeDataUri();
it('adds the correct prefix', function() {
assert.equal(Clay.encodeDataUri('<test>', 'prefix:'), 'prefix:%3Ctest%3E');
assert.equal(
Clay.encodeDataUri('<test>'),
'data:text/html;charset=utf-8,%3Ctest%3E'
);
});
describe('polyfill', function() {
var btoaOriginal = window.btoa;
before(function() {
window.btoa = undefined;
});
testEncodeDataUri();
after(function() {
window.btoa = btoaOriginal;
});
it('encodes the data correctly', function() {
assert.equal(Clay.encodeDataUri('test', ''), 'test');
assert.equal(Clay.encodeDataUri('test{2}', ''), 'test%7B2%7D');
assert.equal(Clay.encodeDataUri('test{10}', ''), 'test%7B10%7D');
});
it('does not throw if unicode character is provided', function() {
assert.doesNotThrow(function() {
Clay.encodeDataUri('♥');
});
});
});
describe('.prepareForAppMessage', function() {
it('converts an array correctly when array contains strings', function() {
assert.deepEqual(
Clay.prepareForAppMessage(['one', 'two', 'three']),
['one', 'two', 'three']
);
assert.deepEqual(
Clay.prepareForAppMessage([{value: 'one'}, 'two', 'three']),
['one', 'two', 'three']);
});
it('converts an array correctly when array contains numbers', function() {
assert.deepEqual(Clay.prepareForAppMessage([1, 2, 3]), [1, 2, 3]);
assert.deepEqual(Clay.prepareForAppMessage(
{value: [1.5, 2.34, 3], precision: 2}),
[150, 234, 300]
);
});
it('converts an array correctly when array contains booleans', function() {
assert.deepEqual(Clay.prepareForAppMessage([true, false, true]), [1, 0, 1]);
assert.deepEqual(
Clay.prepareForAppMessage([{value: true}, false, true]),
[1, 0, 1]);
});
it('converts booleans to ints', function() {
assert.strictEqual(Clay.prepareForAppMessage(false), 0);
assert.strictEqual(Clay.prepareForAppMessage(true), 1);
assert.strictEqual(Clay.prepareForAppMessage({value: false}), 0);
assert.strictEqual(Clay.prepareForAppMessage({value: true}), 1);
});
it('leaves strings alone', function() {
assert.strictEqual(Clay.prepareForAppMessage('test'), 'test');
assert.strictEqual(Clay.prepareForAppMessage({value: 'test'}), 'test');
});
it('Multiples the number by the precision', function() {
assert.strictEqual(Clay.prepareForAppMessage(123), 123);
assert.strictEqual(Clay.prepareForAppMessage({
value: 1.23,
precision: 3
}), 1230);
assert.strictEqual(Clay.prepareForAppMessage({
value: 123,
precision: 2
}), 12300);
assert.strictEqual(Clay.prepareForAppMessage({
value: 1.23456,
precision: 4
}), 12345);
});
it('ignores precision for anything but numbers', function() {
assert.strictEqual(Clay.prepareForAppMessage({
value: 'not a number',
precision: 3
}), 'not a number');
assert.deepEqual(Clay.prepareForAppMessage({
value: ['not', 'a', 'number'],
precision: 3
}), ['not', 'a', 'number']);
});
it('treats an undefined precision as 0', function() {
assert.strictEqual(Clay.prepareForAppMessage({
value: 12.45,
precision: undefined
}), 12);
});
it('Handles sparse arrays', function() {
var sparseArr = [];
sparseArr[1] = 'two';
sparseArr[2] = 'three';
assert.deepEqual(Clay.prepareForAppMessage(sparseArr), sparseArr);
});
});
describe('.prepareSettingsForAppMessage', function() {
it('converts the settings correctly', function() {
var settings = {
test1: false,
'test2[0]': 'val-1',
'test2[1]': 'val-2',
test3: true,
test4: ['cb-1', 'cb-3'],
test5: 12345,
test6: [1, 2, 3, 4],
test7: [true, false, true],
test8: {
precision: 2,
value: 12.34
},
'test9[1]': 'foo'
};
var expected = {
test1: 0,
test2: ['val-1', 'val-2'],
test3: 1,
test4: ['cb-1', 'cb-3'],
test5: 12345,
test6: [1, 2, 3, 4],
test7: [1, 0, 1],
test8: 1234,
test9: [undefined, 'foo']
};
stubMessageKeys(fixture.messageKeysObjToArray(expected));
var result = Clay.prepareSettingsForAppMessage(settings);
assert.deepEqual(result, fixture.messageKeysExpected(expected));
});
it('throws if a 2 dimension array is present', function() {
var settings = {
'test1[1]': ['bad', 'developer!']
};
stubMessageKeys(['test1[2]']);
assert.throws(function() {
Clay.prepareSettingsForAppMessage(settings);
}, /2 dimensional array/i);
});
});
});
+366 -32
View File
@@ -12,10 +12,11 @@ describe('ClayConfig', function() {
it('defines read-only properties', function() {
var properties = [
'EVENTS',
'getItemByAppKey',
'getItemByMessageKey',
'getItemById',
'getItemsByType',
'getSettings',
'getItemsByGroup',
'serialize',
'registerComponent',
'build',
'on',
@@ -27,6 +28,220 @@ describe('ClayConfig', function() {
checkReadOnly(clayConfig, properties);
});
/**
* Tests if the provided capabilities of the platform match
* @param {string} platform
* @param {number} fwMajor
* @param {number} fwMinor
* @param {Array} capabilities
* @param {number} expected
* @param {number} [notExpected] - defaults to the inverse of `expected`
* @return {void}
*/
function testCapabilities(platform, fwMajor, fwMinor, capabilities, expected,
notExpected) {
it(platform + ' is' + (expected ? '' : ' not') + ' included on firmware ' +
fwMajor + '.' + fwMinor + ' for capabilities ' + JSON.stringify(capabilities),
function() {
var item = {
type: 'input',
capabilities: capabilities
};
var meta = {
activeWatchInfo: {
platform: platform,
model: 'qemu_platform_' + platform,
language: 'en_US',
firmware: {
major: fwMajor,
minor: fwMinor,
patch: 0,
suffix: ''
}
}
};
var clayConfig = fixtures.clayConfig([item], true, true, null, meta);
assert.strictEqual(clayConfig.getAllItems().length, expected);
// now we test the NOT_ prefix
item.capabilities = item.capabilities.map(function(capability) {
return 'NOT_' + capability.replace(/^NOT_/, '');
});
notExpected = typeof notExpected !== 'undefined' ? notExpected : ~expected + 2;
clayConfig = fixtures.clayConfig([item], true, true, null, meta);
assert.strictEqual(clayConfig.getAllItems().length, notExpected);
});
}
describe('capability filtering', function() {
testCapabilities('aplite', 2, 9, ['BW'], 1);
testCapabilities('aplite', 2, 9, ['COLOR'], 0);
testCapabilities('aplite', 2, 9, ['MICROPHONE'], 0);
testCapabilities('aplite', 2, 9, ['SMARTSTRAP'], 0);
testCapabilities('aplite', 2, 9, ['SMARTSTRAP_POWER'], 0);
testCapabilities('aplite', 2, 9, ['HEALTH'], 0);
testCapabilities('aplite', 2, 9, ['RECT'], 1);
testCapabilities('aplite', 2, 9, ['ROUND'], 0);
testCapabilities('aplite', 2, 9, ['DISPLAY_144x168'], 1);
testCapabilities('aplite', 2, 9, ['DISPLAY_180x180_ROUND'], 0);
testCapabilities('aplite', 2, 9, ['DISPLAY_200x228'], 0);
testCapabilities('aplite', 2, 9, ['PLATFORM_APLITE'], 1);
testCapabilities('aplite', 2, 9, ['PLATFORM_BASALT'], 0);
testCapabilities('aplite', 2, 9, ['PLATFORM_CHALK'], 0);
testCapabilities('aplite', 2, 9, ['PLATFORM_DIORITE'], 0);
testCapabilities('aplite', 2, 9, ['PLATFORM_EMERY'], 0);
testCapabilities('aplite', 2, 9, ['PLATFORM_FLINT'], 0);
testCapabilities('aplite', 3, 10, ['BW'], 1);
testCapabilities('aplite', 3, 10, ['COLOR'], 0);
testCapabilities('aplite', 3, 10, ['MICROPHONE'], 0);
testCapabilities('aplite', 3, 10, ['SMARTSTRAP'], 0);
testCapabilities('aplite', 3, 4, ['SMARTSTRAP'], 0);
testCapabilities('aplite', 3, 3, ['SMARTSTRAP'], 0);
testCapabilities('aplite', 3, 10, ['SMARTSTRAP_POWER'], 0);
testCapabilities('aplite', 3, 10, ['HEALTH'], 0);
testCapabilities('aplite', 3, 9, ['HEALTH'], 0);
testCapabilities('aplite', 3, 10, ['RECT'], 1);
testCapabilities('aplite', 3, 10, ['ROUND'], 0);
testCapabilities('aplite', 3, 10, ['DISPLAY_144x168'], 1);
testCapabilities('aplite', 3, 10, ['DISPLAY_180x180_ROUND'], 0);
testCapabilities('aplite', 3, 10, ['DISPLAY_200x228'], 0);
testCapabilities('aplite', 3, 10, ['PLATFORM_APLITE'], 1);
testCapabilities('aplite', 3, 10, ['PLATFORM_BASALT'], 0);
testCapabilities('aplite', 3, 10, ['PLATFORM_CHALK'], 0);
testCapabilities('aplite', 3, 10, ['PLATFORM_DIORITE'], 0);
testCapabilities('aplite', 3, 10, ['PLATFORM_EMERY'], 0);
testCapabilities('aplite', 3, 10, ['PLATFORM_FLINT'], 0);
testCapabilities('basalt', 3, 10, ['BW'], 0);
testCapabilities('basalt', 3, 10, ['COLOR'], 1);
testCapabilities('basalt', 3, 10, ['MICROPHONE'], 1);
testCapabilities('basalt', 3, 10, ['SMARTSTRAP'], 1);
testCapabilities('basalt', 3, 4, ['SMARTSTRAP'], 1);
testCapabilities('basalt', 3, 3, ['SMARTSTRAP'], 0);
testCapabilities('basalt', 3, 10, ['SMARTSTRAP_POWER'], 1);
testCapabilities('basalt', 3, 4, ['SMARTSTRAP_POWER'], 1);
testCapabilities('basalt', 3, 3, ['SMARTSTRAP_POWER'], 0);
testCapabilities('basalt', 3, 10, ['HEALTH'], 1);
testCapabilities('basalt', 3, 9, ['HEALTH'], 0);
testCapabilities('basalt', 3, 10, ['RECT'], 1);
testCapabilities('basalt', 3, 10, ['ROUND'], 0);
testCapabilities('basalt', 3, 10, ['DISPLAY_144x168'], 1);
testCapabilities('basalt', 3, 10, ['DISPLAY_180x180_ROUND'], 0);
testCapabilities('basalt', 3, 10, ['DISPLAY_200x228'], 0);
testCapabilities('basalt', 3, 10, ['PLATFORM_APLITE'], 0);
testCapabilities('basalt', 3, 10, ['PLATFORM_BASALT'], 1);
testCapabilities('basalt', 3, 10, ['PLATFORM_CHALK'], 0);
testCapabilities('basalt', 3, 10, ['PLATFORM_DIORITE'], 0);
testCapabilities('basalt', 3, 10, ['PLATFORM_EMERY'], 0);
testCapabilities('basalt', 3, 10, ['PLATFORM_FLINT'], 0);
testCapabilities('chalk', 3, 10, ['BW'], 0);
testCapabilities('chalk', 3, 10, ['COLOR'], 1);
testCapabilities('chalk', 3, 10, ['MICROPHONE'], 1);
testCapabilities('chalk', 3, 10, ['SMARTSTRAP'], 1);
testCapabilities('chalk', 3, 4, ['SMARTSTRAP'], 1);
testCapabilities('chalk', 3, 3, ['SMARTSTRAP'], 0);
testCapabilities('chalk', 3, 10, ['SMARTSTRAP_POWER'], 1);
testCapabilities('chalk', 3, 4, ['SMARTSTRAP_POWER'], 1);
testCapabilities('chalk', 3, 3, ['SMARTSTRAP_POWER'], 0);
testCapabilities('chalk', 3, 10, ['HEALTH'], 1);
testCapabilities('chalk', 3, 9, ['HEALTH'], 0);
testCapabilities('chalk', 3, 10, ['RECT'], 0);
testCapabilities('chalk', 3, 10, ['ROUND'], 1);
testCapabilities('chalk', 3, 10, ['DISPLAY_144x168'], 0);
testCapabilities('chalk', 3, 10, ['DISPLAY_180x180_ROUND'], 1);
testCapabilities('chalk', 3, 10, ['DISPLAY_200x228'], 0);
testCapabilities('chalk', 3, 10, ['PLATFORM_APLITE'], 0);
testCapabilities('chalk', 3, 10, ['PLATFORM_BASALT'], 0);
testCapabilities('chalk', 3, 10, ['PLATFORM_CHALK'], 1);
testCapabilities('chalk', 3, 10, ['PLATFORM_DIORITE'], 0);
testCapabilities('chalk', 3, 10, ['PLATFORM_EMERY'], 0);
testCapabilities('chalk', 3, 10, ['PLATFORM_FLINT'], 0);
testCapabilities('diorite', 3, 10, ['BW'], 1);
testCapabilities('diorite', 3, 10, ['COLOR'], 0);
testCapabilities('diorite', 3, 10, ['MICROPHONE'], 1);
testCapabilities('diorite', 3, 10, ['SMARTSTRAP'], 1);
testCapabilities('diorite', 3, 4, ['SMARTSTRAP'], 1);
testCapabilities('diorite', 3, 3, ['SMARTSTRAP'], 0);
testCapabilities('diorite', 3, 10, ['SMARTSTRAP_POWER'], 0);
testCapabilities('diorite', 3, 4, ['SMARTSTRAP_POWER'], 0);
testCapabilities('diorite', 3, 3, ['SMARTSTRAP_POWER'], 0);
testCapabilities('diorite', 3, 10, ['HEALTH'], 1);
testCapabilities('diorite', 3, 9, ['HEALTH'], 0);
testCapabilities('diorite', 3, 10, ['RECT'], 1);
testCapabilities('diorite', 3, 10, ['ROUND'], 0);
testCapabilities('diorite', 3, 10, ['DISPLAY_144x168'], 1);
testCapabilities('diorite', 3, 10, ['DISPLAY_180x180_ROUND'], 0);
testCapabilities('diorite', 3, 10, ['DISPLAY_200x228'], 0);
testCapabilities('diorite', 3, 10, ['PLATFORM_APLITE'], 0);
testCapabilities('diorite', 3, 10, ['PLATFORM_BASALT'], 0);
testCapabilities('diorite', 3, 10, ['PLATFORM_CHALK'], 0);
testCapabilities('diorite', 3, 10, ['PLATFORM_DIORITE'], 1);
testCapabilities('diorite', 3, 10, ['PLATFORM_EMERY'], 0);
testCapabilities('diorite', 3, 10, ['PLATFORM_FLINT'], 0);
testCapabilities('emery', 3, 10, ['BW'], 0);
testCapabilities('emery', 3, 10, ['COLOR'], 1);
testCapabilities('emery', 3, 10, ['MICROPHONE'], 1);
testCapabilities('emery', 3, 10, ['SMARTSTRAP'], 1);
testCapabilities('emery', 3, 4, ['SMARTSTRAP'], 1);
testCapabilities('emery', 3, 3, ['SMARTSTRAP'], 0);
testCapabilities('emery', 3, 10, ['SMARTSTRAP_POWER'], 1);
testCapabilities('emery', 3, 4, ['SMARTSTRAP_POWER'], 1);
testCapabilities('emery', 3, 3, ['SMARTSTRAP_POWER'], 0);
testCapabilities('emery', 3, 10, ['HEALTH'], 1);
testCapabilities('emery', 3, 9, ['HEALTH'], 0);
testCapabilities('emery', 3, 10, ['RECT'], 1);
testCapabilities('emery', 3, 10, ['ROUND'], 0);
testCapabilities('emery', 3, 10, ['DISPLAY_144x168'], 0);
testCapabilities('emery', 3, 10, ['DISPLAY_180x180_ROUND'], 0);
testCapabilities('emery', 3, 10, ['DISPLAY_200x228'], 1);
testCapabilities('emery', 3, 10, ['PLATFORM_APLITE'], 0);
testCapabilities('emery', 3, 10, ['PLATFORM_BASALT'], 0);
testCapabilities('emery', 3, 10, ['PLATFORM_CHALK'], 0);
testCapabilities('emery', 3, 10, ['PLATFORM_DIORITE'], 0);
testCapabilities('emery', 3, 10, ['PLATFORM_EMERY'], 1);
testCapabilities('emery', 3, 10, ['PLATFORM_FLINT'], 0);
testCapabilities('flint', 3, 10, ['BW'], 1);
testCapabilities('flint', 3, 10, ['COLOR'], 0);
testCapabilities('flint', 3, 10, ['MICROPHONE'], 1);
testCapabilities('flint', 3, 10, ['SMARTSTRAP'], 0);
testCapabilities('flint', 3, 4, ['SMARTSTRAP'], 0);
testCapabilities('flint', 3, 3, ['SMARTSTRAP'], 0);
testCapabilities('flint', 3, 10, ['SMARTSTRAP_POWER'], 0);
testCapabilities('flint', 3, 4, ['SMARTSTRAP_POWER'], 0);
testCapabilities('flint', 3, 3, ['SMARTSTRAP_POWER'], 0);
testCapabilities('flint', 3, 10, ['HEALTH'], 1);
testCapabilities('flint', 3, 9, ['HEALTH'], 0);
testCapabilities('flint', 3, 10, ['RECT'], 1);
testCapabilities('flint', 3, 10, ['ROUND'], 0);
testCapabilities('flint', 3, 10, ['DISPLAY_144x168'], 1);
testCapabilities('flint', 3, 10, ['DISPLAY_180x180_ROUND'], 0);
testCapabilities('flint', 3, 10, ['DISPLAY_200x228'], 0);
testCapabilities('flint', 3, 10, ['PLATFORM_APLITE'], 0);
testCapabilities('flint', 3, 10, ['PLATFORM_BASALT'], 0);
testCapabilities('flint', 3, 10, ['PLATFORM_CHALK'], 0);
testCapabilities('flint', 3, 10, ['PLATFORM_DIORITE'], 0);
testCapabilities('flint', 3, 10, ['PLATFORM_EMERY'], 0);
testCapabilities('flint', 3, 10, ['PLATFORM_FLINT'], 1);
testCapabilities('aplite', 3, 10, ['BW', 'HEALTH'], 0, 0);
testCapabilities('emery', 3, 10, ['COLOR', 'HEALTH'], 1);
testCapabilities('emery', 3, 10, ['MICROPHONE'], 1);
testCapabilities('chalk', 3, 10, ['PLATFORM_APLITE', 'PLATFORM_BASALT'], 0);
testCapabilities('basalt', 3, 10, ['SMARTSTRAP', 'SMARTSTRAP_POWER'], 1);
testCapabilities('aplite', 3, 10, ['COLOR', 'PLATFORM_APLITE'], 0, 0);
testCapabilities('aplite', 3, 10, ['PLATFORM_APLITE', 'COLOR'], 0, 0);
testCapabilities('aplite', 3, 10, ['BW', 'NOT_COLOR'], 1, 0);
testCapabilities('aplite', 3, 10, ['NOT_BASALT', 'RECT'], 1, 0);
testCapabilities('aplite', 3, 10, ['NOT_CHALK', 'BW', 'PLATFORM_APLITE'], 1, 0);
});
describe('.meta', function() {
it('populates meta', function() {
var clayConfig = fixtures.clayConfig(['input']);
@@ -36,10 +251,11 @@ describe('ClayConfig', function() {
describe('throws when trying to run methods before being built', function() {
[
'getItemByAppKey',
'getItemByMessageKey',
'getItemById',
'getItemsByType',
'getSettings'
'getItemsByGroup',
'serialize'
].forEach(function(method) {
it('.' + method + '()', function() {
var clayConfig = fixtures.clayConfig(['input', 'text'], false);
@@ -64,14 +280,17 @@ describe('ClayConfig', function() {
});
});
describe('.getItemByAppKey()', function() {
describe('.getItemByMessageKey()', function() {
it('it returns the correct item', function() {
var config = fixtures.config([
{type: 'input', appKey: 'test-app-key', clayId: 0},
{type: 'input', appKey: undefined, clayId: 1}
{type: 'input', messageKey: 'test-app-key', clayId: 0},
{type: 'input', messageKey: undefined, clayId: 1}
]);
var clayConfig = fixtures.clayConfig(config);
assert.deepEqual(clayConfig.getItemByAppKey('test-app-key').config, config[0]);
assert.deepEqual(
clayConfig.getItemByMessageKey('test-app-key').config,
config[0]
);
});
});
@@ -99,20 +318,40 @@ describe('ClayConfig', function() {
});
});
describe('.getSettings()', function() {
describe('.getItemsByGroup()', function() {
it('it returns the correct items', function() {
var config = fixtures.config([
{type: 'input', id: 'g1-0', group: 'group1'},
{type: 'text', id: 'g2-0', group: 'group2'},
{type: 'input', id: 'g1-1', group: 'group1'}
]);
var clayConfig = fixtures.clayConfig(config);
assert.deepEqual(clayConfig.getItemsByGroup('group1'), [
clayConfig.getItemById('g1-0'),
clayConfig.getItemById('g1-1')
]);
assert.deepEqual(clayConfig.getItemsByGroup('group2'), [
clayConfig.getItemById('g2-0')
]);
});
});
describe('.serialize()', function() {
it('returns the correct settings', function() {
var config = [
{type: 'input', appKey: 'test1', defaultValue: 'default val'},
{type: 'select', appKey: 'test2', options: [
{type: 'input', messageKey: 'test1', defaultValue: 'default val'},
{type: 'select', messageKey: 'test2', options: [
{label: 'label-1', value: 'val-1'},
{label: 'label-2', value: 'val-2'}
]},
{type: 'toggle', appKey: 'test3'},
{type: 'checkboxgroup', appKey: 'test4', options: [
{label: 'label-1', value: 'cb-1'},
{label: 'label-2', value: 'cb-2'},
{label: 'label-2', value: 'cb-3'}
]}
{type: 'toggle', messageKey: 'test3'},
{type: 'checkboxgroup', messageKey: 'test4', options: [
'label-1',
'label-2',
'label-3'
]},
{type: 'slider', messageKey: 'test5', step: 0.05, defaultValue: 12.5}
];
var settings = {
test2: 'val-2'
@@ -120,29 +359,62 @@ describe('ClayConfig', function() {
var clayConfig = fixtures.clayConfig(config, true, true, settings);
assert.deepEqual(clayConfig.getSettings(), {
test1: 'default val',
test2: 'val-2',
test3: false,
test4: []
assert.deepEqual(clayConfig.serialize(), {
test1: {value: 'default val'},
test2: {value: 'val-2'},
test3: {value: false},
test4: {value: [false, false, false]},
test5: {value: 12.5, precision: 2}
});
clayConfig.getItemByAppKey('test1').set('val-1');
clayConfig.getItemByAppKey('test3').set(true);
clayConfig.getItemByAppKey('test4').set(['cb-1', 'cb-3']);
clayConfig.getItemByMessageKey('test1').set('val-1');
clayConfig.getItemByMessageKey('test3').set(true);
clayConfig.getItemByMessageKey('test4').set([true, false, true]);
assert.deepEqual(clayConfig.getSettings(), {
assert.deepEqual(clayConfig.serialize(), {
test1: {value: 'val-1'},
test2: {value: 'val-2'},
test3: {value: true},
test4: {value: [true, false, true]},
test5: {value: 12.5, precision: 2}
});
// make sure the result of serialize() can actually be fed back in to
// a new instance of ClayConfig
var clay = fixtures.clay(config);
var response = encodeURIComponent(JSON.stringify(clayConfig.serialize()));
clay.getSettings(response);
var loadedSettings = JSON.parse(localStorage.getItem('clay-settings'));
assert.deepEqual(loadedSettings, {
test1: 'val-1',
test2: 'val-2',
test3: true,
test4: ['cb-1', 'cb-3']
test4: [true, false, true],
test5: 12.5
});
// make sure the result of getSettings() can actually be fed back in to
// a new instance of ClayConfig
assert.doesNotThrow(function() {
settings = clayConfig.getSettings();
fixtures.clayConfig(config, true, true, settings);
fixtures.clayConfig(config, true, true, loadedSettings);
});
});
it('only returns the settings present in the config', function() {
var config = [
{type: 'input', messageKey: 'test1', defaultValue: 'default val'},
{type: 'select', messageKey: 'test2', options: [
{label: 'label-1', value: 'val-1'},
{label: 'label-2', value: 'val-2'}
]}
];
var settings = {
test2: 'val-2',
notInTheConfig: 'Should not exist'
};
var clayConfig = fixtures.clayConfig(config, true, true, settings);
assert.deepEqual(clayConfig.serialize(), {
test1: {value: 'default val'},
test2: {value: 'val-2'}
});
});
});
@@ -240,7 +512,69 @@ describe('ClayConfig', function() {
});
});
describe('.destroy().', function() {
it('Destroys all the items on the page and in the items array', function() {
var clayConfig = fixtures.clayConfig(['input', 'text', 'input'], false);
clayConfig.build();
clayConfig.destroy();
// this should throw because the config has not been built yet
assert.throws(clayConfig.getAllItems);
// there should be no DOM inside the container
assert.strictEqual(clayConfig.$rootContainer[0].children.length, 0);
});
it('dispatches the BEFORE_DESTROY event at the right time', function(done) {
var clayConfig = fixtures.clayConfig(['input', 'text', 'input'], false);
clayConfig.on(clayConfig.EVENTS.BEFORE_DESTROY, function() {
assert.strictEqual(clayConfig.getAllItems().length, 3);
done();
});
clayConfig.build();
clayConfig.destroy();
assert.strictEqual(clayConfig.getAllItems().length, 0);
});
it('dispatches the AFTER_DESTROY event at the right time', function(done) {
var clayConfig = fixtures.clayConfig(['input', 'text', 'input'], false);
clayConfig.on(clayConfig.EVENTS.AFTER_DESTROY, function() {
// this should throw because the config has not been built yet
assert.throws(clayConfig.getAllItems);
done();
});
clayConfig.build();
assert.strictEqual(clayConfig.getAllItems().length, 3);
clayConfig.destroy();
});
});
describe('.build()', function() {
it('Destroys the config if called a consecutive time', function() {
var clayConfig = fixtures.clayConfig(['input', 'text', 'input'], false);
var destroyHandlerSpy = sinon.spy();
clayConfig.on(clayConfig.EVENTS.AFTER_DESTROY, destroyHandlerSpy);
clayConfig.build();
assert.strictEqual(clayConfig.getAllItems().length, 3);
clayConfig.config = fixtures.config(['select']);
clayConfig.build();
assert.strictEqual(clayConfig.getAllItems().length, 1);
assert(destroyHandlerSpy.calledOnce);
clayConfig.build();
assert(destroyHandlerSpy.calledTwice);
});
it('dispatches the BEFORE_BUILD event at the right time', function(done) {
var clayConfig = fixtures.clayConfig(['input', 'text', 'input'], false);
clayConfig.on(clayConfig.EVENTS.BEFORE_BUILD, function() {
+12 -10
View File
@@ -12,7 +12,7 @@ describe('ClayItem', function() {
it('defines read-only properties', function() {
var properties = [
'id',
'appKey',
'messageKey',
'config',
'$element',
'$manipulatorTarget',
@@ -53,24 +53,25 @@ describe('ClayItem', function() {
});
});
describe('.appKey', function() {
it('sets appKey correctly', function() {
describe('.messageKey', function() {
it('sets messageKey correctly', function() {
var config = fixture.configItem('input');
var clayItem = new ClayItem(config);
assert.strictEqual(clayItem.appKey, config.appKey);
assert.strictEqual(clayItem.messageKey, config.messageKey);
});
it('sets appKey to null if there is no appKey in the config', function() {
var clayItem = fixture.clayItem({type: 'input', appKey: undefined});
assert.strictEqual(clayItem.appKey, null);
it('sets messageKey to null if there is no messageKey in the config',
function() {
var clayItem = fixture.clayItem({type: 'input', messageKey: undefined});
assert.strictEqual(clayItem.messageKey, null);
});
});
describe('.config', function() {
it('sets appKey correctly', function() {
it('sets messageKey correctly', function() {
var config = fixture.configItem('input');
var clayItem = new ClayItem(config);
assert.strictEqual(clayItem.appKey, config.appKey);
assert.strictEqual(clayItem.messageKey, config.messageKey);
});
});
@@ -104,7 +105,8 @@ describe('ClayItem', function() {
it('returns itself for chaining', function() {
var clayItem = fixture.clayItem('select');
assert.strictEqual(clayItem.initialize(), clayItem);
var clayConfig = fixture.clayConfig([]);
assert.strictEqual(clayItem.initialize(clayConfig), clayItem);
});
it('does nothing if there is no initialize function', function() {
+87 -59
View File
@@ -143,43 +143,67 @@ describe('manipulators', function() {
}
describe('html', function() {
testSetGet('text', 'test123');
testSetGet('button', '<span>some HTML</span>');
testShow('text');
testHide('text');
var type = 'text';
testSetGet(type, 'test123');
testSetGet(type, '<span>some HTML</span>');
testShow(type);
testHide(type);
});
describe('button', function() {
testSetGet('button', 'test123');
testSetGet('button', '<span>some HTML</span>');
testDisable('button');
testEnable('button');
testShow('button');
testHide('button');
var type = 'button';
testSetGet(type, 'test123');
testSetGet(type, '<span>some HTML</span>');
testDisable(type);
testEnable(type);
testShow(type);
testHide(type);
});
describe('val', function() {
testSetGet('input', 'test321');
testSetGet('input', 1234, '1234');
testDisable('input');
testEnable('input');
testShow('text');
testHide('text');
var type = 'input';
testSetGet(type, 'test321');
testSetGet(type, 1234, '1234');
testDisable(type);
testEnable(type);
testShow(type);
testHide(type);
});
describe('slider', function() {
var type = {
type: 'slider',
min: 0,
max: 100,
step: 0.1
};
testSetGet(type, '12', 12);
testSetGet(type, 12);
testSetGet(type, '12.3', 12.3);
testSetGet(type, 12.3);
testSetGet(type, 12.34, 12.3);
testSetGet(type, '12.34', 12.3);
testDisable(type);
testEnable(type);
testShow(type);
testHide(type);
});
describe('checked', function() {
testSetGet({type: 'toggle', defaultValue: false}, 1, true);
testSetGet({type: 'toggle', defaultValue: false}, true);
testSetGet({type: 'toggle', defaultValue: true}, 0, false);
testSetGet({type: 'toggle', defaultValue: true}, false);
testDisable('toggle');
testEnable('toggle');
testShow('toggle');
testHide('toggle');
var type = 'toggle';
testSetGet({type: type, defaultValue: false}, 1, true);
testSetGet({type: type, defaultValue: false}, true);
testSetGet({type: type, defaultValue: true}, 0, false);
testSetGet({type: type, defaultValue: true}, false);
testDisable(type);
testEnable(type);
testShow(type);
testHide(type);
});
describe('radiogroup', function() {
var item = {
var type = {
type: 'radiogroup',
clayId: 1,
options: [
@@ -188,48 +212,52 @@ describe('manipulators', function() {
{ label: '3', value: 'three "quote' }
]
};
testSetGet(item, 'one');
testSetGet(item, 'two');
testSetGet(item, 'three "quote');
testDisable(item);
testEnable(item);
testShow(item);
testHide(item);
testSetGet(type, 'one');
testSetGet(type, 'two');
testSetGet(type, 'three "quote');
testDisable(type);
testEnable(type);
testShow(type);
testHide(type);
});
describe('checkboxgroup', function() {
var item = {
var type = {
type: 'checkboxgroup',
clayId: 1,
defaultValue: ['two'],
options: [
{ label: '1', value: 'one' },
{ label: '2', value: 'two' },
{ label: '3', value: 'three "quote' }
]
defaultValue: [true, true, true],
options: ['First', 'Second', 'Third']
};
testSetGet(item, ['one', 'two']);
testSetGet(item, ['three "quote']);
testSetGet(item, []);
testSetGet(item, false, []);
testDisable(item);
testEnable(item);
testShow(item);
testHide(item);
testSetGet(type, [false, false, true]);
testSetGet(type, [true, false], [true, false, false]);
testSetGet(type, [1, 0], [true, false, false]);
testSetGet(type, [true], [true, false, false]);
testSetGet(type, [1], [true, false, false]);
testSetGet(type, [], [false, false, false]);
// any non-array values should result in all false
testSetGet(type, false, [false, false, false]);
testSetGet(type, true, [false, false, false]);
testSetGet(type, null, [false, false, false]);
testDisable(type);
testEnable(type);
testShow(type);
testHide(type);
});
describe('color', function() {
testSetGet('color', 'FF0000', 0xff0000);
testSetGet('color', '#FF0000', 0xff0000);
testSetGet('color', '0xFF0000', 0xff0000);
testSetGet('color', '#ff0000', 0xff0000);
testSetGet('color', 0xff0000, 0xff0000);
testSetGet({type: 'color', defaultValue: 0x00ff00}, '', 0x000000);
testSetGet({type: 'color', defaultValue: 0x00ff00}, false, 0x000000);
testSetGet({type: 'color', defaultValue: 0x00ff00}, undefined, 0x000000);
testDisable('color');
testEnable('color');
testShow('color');
testHide('color');
var type = 'color';
testSetGet(type, 'FF0000', 0xff0000);
testSetGet(type, '#FF0000', 0xff0000);
testSetGet(type, '0xFF0000', 0xff0000);
testSetGet(type, '#ff0000', 0xff0000);
testSetGet(type, 0xff0000, 0xff0000);
testSetGet({type: type, defaultValue: 0x00ff00}, '', 0x000000);
testSetGet({type: type, defaultValue: 0x00ff00}, false, 0x000000);
testSetGet({type: type, defaultValue: 0x00ff00}, undefined, 0x000000);
testDisable(type);
testEnable(type);
testShow(type);
testHide(type);
});
});
-55
View File
@@ -26,60 +26,5 @@ describe('Utils', function() {
);
});
});
describe('.prepareForAppMessage', function() {
it('converts an array correctly when array contains strings', function() {
assert.deepEqual(
utils.prepareForAppMessage(['one', 'two']),
['one', 0, 'two', 0]
);
});
it('converts an array correctly when array contains numbers', function() {
assert.deepEqual(utils.prepareForAppMessage([1, 2, 3]), [1, 2, 3]);
});
it('converts an array correctly when array contains booleans', function() {
assert.deepEqual(utils.prepareForAppMessage([true, false, true]), [1, 0, 1]);
});
it('converts booleans to ints', function() {
assert.strictEqual(utils.prepareForAppMessage(false), 0);
assert.strictEqual(utils.prepareForAppMessage(true), 1);
});
it('leaves strings alone', function() {
assert.strictEqual(utils.prepareForAppMessage('test'), 'test');
});
it('leaves numbers alone', function() {
assert.strictEqual(utils.prepareForAppMessage(123), 123);
});
});
describe('.prepareSettingsForAppMessage', function() {
it('converts the settings correctly', function() {
var settings = {
test1: false,
test2: 'val-2',
test3: true,
test4: ['cb-1', 'cb-3'],
test5: 12345,
test6: [1, 2, 3, 4],
test7: [true, false, true]
};
var expected = {
test1: 0,
test2: 'val-2',
test3: 1,
test4: ['cb-1', 0, 'cb-3', 0],
test5: 12345,
test6: [1, 2, 3, 4],
test7: [1, 0, 1]
};
assert.deepEqual(utils.prepareSettingsForAppMessage(settings), expected);
});
});
});
+48
View File
@@ -0,0 +1,48 @@
#
# This file is the default set of rules to compile a Pebble project.
#
# Feel free to customize this to your needs.
#
import os
import shutil
import waflib
top = '.'
out = 'build'
def distclean(ctx):
if os.path.exists('dist.zip'):
os.remove('dist.zip')
if os.path.exists('dist'):
shutil.rmtree('dist')
waflib.Scripting.distclean(ctx)
def options(ctx):
ctx.load('pebble_sdk_lib')
def configure(ctx):
ctx.load('pebble_sdk_lib')
def build(ctx):
ctx.load('pebble_sdk_lib')
cached_env = ctx.env
for platform in ctx.env.TARGET_PLATFORMS:
ctx.env = ctx.all_envs[platform]
ctx.set_group(ctx.env.PLATFORM_NAME)
lib_name = '{}/{}'.format(ctx.env.BUILD_DIR, ctx.env.PROJECT_INFO['name'])
ctx.pbl_build(source=ctx.path.ant_glob('src/c/**/*.c'), target=lib_name, bin_type='lib')
ctx.env = cached_env
ctx.set_group('bundle')
ctx.pbl_bundle(includes=ctx.path.ant_glob('include/**/*.h'),
js=ctx.path.ant_glob(['src/js/**/*.js', 'src/js/**/*.json']),
bin_type='lib')
if ctx.cmd == 'clean':
for n in ctx.path.ant_glob(['dist/**/*', 'dist.zip'], quiet=True):
n.delete()