├── .gitignore
├── .npmignore
├── CHANGELOG.md
├── Jenkinsfile
├── LICENSE
├── README.md
├── RELEASENOTES.md
├── config
├── mocha.opts
└── tsconfig.build.json
├── docs
├── RELEASENOTES.template.md
├── logo.png
└── logo.svg
├── examples
├── .gitignore
├── initializer-and-query
│ ├── .gitignore
│ ├── README.md
│ ├── package-lock.json
│ ├── package.json
│ └── src
│ │ ├── App.js
│ │ ├── App.test.js
│ │ └── SomeDeeplyNestedComponent.js
├── multiple-realms
│ ├── .gitignore
│ ├── README.md
│ ├── package-lock.json
│ ├── package.json
│ └── src
│ │ ├── App.js
│ │ ├── App.test.js
│ │ ├── CarsRealm.js
│ │ ├── PersonsRealm.js
│ │ └── SomeChildComponent.js
├── simple-context
│ ├── .gitignore
│ ├── README.md
│ ├── package-lock.json
│ ├── package.json
│ └── src
│ │ ├── App.js
│ │ ├── App.test.js
│ │ └── SomeDeeplyNestedComponent.js
└── simple-render-props
│ ├── .gitignore
│ ├── README.md
│ ├── package-lock.json
│ ├── package.json
│ └── src
│ ├── App.js
│ └── App.test.js
├── integration-tests
├── environment.ts
├── environments.test.ts
└── examples.test.ts
├── package-lock.json
├── package.json
├── scripts
├── fix-headers.js
├── github-releases.js
├── header.txt
└── next-version.js
├── src
├── @types
│ ├── memoize-one
│ │ └── index.d.ts
│ └── segfault-handler
│ │ └── index.d.ts
├── RealmConnection.test.tsx
├── RealmConnection.tsx
├── RealmConsumer.test.tsx
├── RealmConsumer.tsx
├── RealmInitializer.test.tsx
├── RealmInitializer.tsx
├── RealmProgress.test.tsx
├── RealmProgress.tsx
├── RealmProvider.test.tsx
├── RealmProvider.tsx
├── RealmQuery.1-basic.test.tsx
├── RealmQuery.2-filter.test.tsx
├── RealmQuery.3-sort.test.tsx
├── RealmQuery.4-type-prop.test.tsx
├── RealmQuery.5-filter-prop.test.tsx
├── RealmQuery.6-sort-prop.test.tsx
├── RealmQuery.7-swapped-realm.test.tsx
├── RealmQuery.tsx
├── index.test.tsx
├── index.tsx
├── test-utils
│ ├── persons-realm.ts
│ ├── realm-js-logging.ts
│ ├── segfault-handler.ts
│ └── with-ros.ts
├── withRealm.test.tsx
└── withRealm.tsx
├── tsconfig.json
└── tslint.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # Node modules
2 | node_modules/
3 |
4 | # Build artifacts
5 | build/
6 | react-realm-context-*.tgz
7 |
8 | # Documentation
9 | generated-docs/
10 |
11 | # Test results
12 | test-results.xml
13 |
14 | # Realm files generated by tests
15 | *.realm
16 | *.realm.lock
17 | *.realm.management
18 | realm-object-server/
19 |
20 | # Environments generated by tests
21 | integration-tests/environments/
22 | .DS_Store
23 | /crash.log
24 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # Ignore everything
2 | *
3 |
4 | # Except artifacts
5 | !build/*
6 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Release 0.3.0 (2019-02-28)
2 |
3 | [Changes since v0.2.0](https://github.com/realm/react-realm-context/compare/v0.2.0...v0.3.0)
4 |
5 | ## Enhancements
6 |
7 | * None
8 |
9 | ## Fixed
10 |
11 | * Fixed the types by removing the `esModuleInterop` from the tsconfig.json. ([#15](https://github.com/realm/react-realm-context/pull/15), since 0.2.0)
12 |
13 | ## Internals
14 |
15 | * None
16 |
17 |
18 | # Release 0.2.0 (2019-02-28)
19 |
20 | [Changes since v0.1.0](https://github.com/realm/react-realm-context/compare/v0.1.0...v0.2.0)
21 |
22 | ## Enhancements
23 |
24 | * Added a `RealmProgress` component which will report progress when up or downloading. ([#14](https://github.com/realm/react-realm-context/pull/14))
25 |
26 | ## Fixed
27 |
28 | * Fixed how context consuming components reacted to a Realm instance being swapped by the RealmProvider. ([#12](https://github.com/realm/react-realm-context/pull/12), since 0.1.0)
29 |
30 | ## Internals
31 |
32 | * Added documentation using tsdocs published to GitHub pages on https://realm.github.io/react-realm-context. ([#10](https://github.com/realm/react-realm-context/pull/10))
33 | * Added better component oriented docs to the README.md. ([#11](https://github.com/realm/react-realm-context/pull/11))
34 | * Upgraded dependencies, restructured test utilities and fixed the ROS dependent tests. ([#13](https://github.com/realm/react-realm-context/pull/13))
35 |
36 |
37 | # Release 0.1.0 (2018-12-14)
38 |
39 | ## Enhancements
40 |
41 | * Initial release of React Realm Context, see [README.md](https://github.com/realm/react-realm-context/blob/master/README.md) for its features.
42 |
43 | ## Fixed
44 |
45 | * None
46 |
47 | ## Internals
48 |
49 | * None
50 |
--------------------------------------------------------------------------------
/Jenkinsfile:
--------------------------------------------------------------------------------
1 | #!groovy
2 |
3 | import groovy.json.JsonOutput
4 |
5 | // Changes the version in the package.json and package-lock.json
6 | def changeVersion(String preId = "") {
7 | // Determine the upcoming release type
8 | nextVersionType = sh(
9 | script: "node ./scripts/next-version.js",
10 | returnStdout: true,
11 | ).trim()
12 | // Ask NPM to update the package json and lock and read the next version
13 | // If a preid is specified, perform a pre-release afterwards
14 | if (preId) {
15 | // Update the version of the package again
16 | nextVersion = sh(
17 | script: "npm version pre${nextVersionType} --no-git-tag-version --preid=${preId}",
18 | returnStdout: true,
19 | ).trim()
20 | } else {
21 | nextVersion = sh(
22 | script: "npm version ${nextVersionType} --no-git-tag-version",
23 | returnStdout: true,
24 | ).trim()
25 | }
26 | // Set the build name
27 | currentBuild.displayName += ": ${nextVersion}"
28 | return nextVersion
29 | }
30 |
31 | def packAndArchive() {
32 | // Remove any archives produced by the tests
33 | sh 'rm -f react-realm-context-*.tgz'
34 | // Ignore the prepack running "build" again
35 | sh 'npm pack --ignore-scripts'
36 | // Archive the archive
37 | archiveArtifacts 'react-realm-context-*.tgz'
38 | }
39 |
40 | def copyReleaseNotes(versionBefore, versionAfter) {
41 | // Read the release notes and replace in any variables
42 | releaseNotes = readFile 'RELEASENOTES.md'
43 | releaseNotes = releaseNotes
44 | .replaceAll("\\{PREVIOUS_VERSION\\}", versionBefore)
45 | .replaceAll("\\{CURRENT_VERSION\\}", versionAfter)
46 | // Write back the release notes
47 | writeFile file: 'RELEASENOTES.md', text: releaseNotes
48 |
49 | // Get todays date
50 | today = new Date().format('yyyy-MM-dd')
51 | // Append the release notes to the change log
52 | changeLog = readFile 'CHANGELOG.md'
53 | writeFile(
54 | file: 'CHANGELOG.md',
55 | text: "# Release ${versionAfter.substring(1)} (${today})\n\n${releaseNotes}\n\n${changeLog}",
56 | )
57 | }
58 |
59 | def testExampleApp(String app) {
60 | dir("examples/$app") {
61 | sh 'npm install ../../react-realm-context-*.tgz --no-save'
62 | sh 'npm test -- --forceExit'
63 | }
64 | }
65 |
66 | pipeline {
67 | agent {
68 | docker {
69 | image 'node:8'
70 | label 'docker'
71 | // /etc/passwd is mapped so a jenkins users is available from within the container
72 | // ~/.ssh is mapped to allow pushing to GitHub via SSH
73 | args '-e "HOME=${WORKSPACE}" -v /etc/passwd:/etc/passwd:ro -v /home/jenkins/.ssh:/home/jenkins/.ssh:ro'
74 | }
75 | }
76 |
77 | environment {
78 | // Tells Jest (used for example app tests) to run non-interactive
79 | CI = true
80 | // Parameters used by the github releases script
81 | GITHUB_OWNER="realm"
82 | GITHUB_REPO="react-realm-context"
83 | }
84 |
85 | options {
86 | // Prevent checking out multiple times
87 | skipDefaultCheckout()
88 | }
89 |
90 | parameters {
91 | booleanParam(
92 | name: 'PREPARE',
93 | defaultValue: false,
94 | description: '''Prepare for publishing?
95 | Changes version based on release notes,
96 | copies release notes to changelog,
97 | creates a draft GitHub release and
98 | pushes a tagged commit to git.
99 | ''',
100 | )
101 | }
102 |
103 | stages {
104 | stage('Checkout') {
105 | steps {
106 | checkout([
107 | $class: 'GitSCM',
108 | branches: scm.branches,
109 | extensions: scm.extensions + [
110 | [$class: 'WipeWorkspace'],
111 | [$class: 'CleanCheckout'],
112 | [$class: 'LocalBranch']
113 | ],
114 | userRemoteConfigs: [[
115 | credentialsId: 'realm-ci-ssh',
116 | name: 'origin',
117 | url: 'git@github.com:realm/react-realm-context.git'
118 | ]]
119 | ])
120 |
121 | // Set the email and name used when committing
122 | sh 'git config --global user.email "ci@realm.io"'
123 | sh 'git config --global user.name "Jenkins CI"'
124 |
125 | // Setting the TAG_NAME env as this is not happening when skipping default checkout.
126 | script {
127 | env.TAG_NAME = sh(
128 | script: 'git tag --points-at HEAD',
129 | returnStdout: true,
130 | ).trim()
131 | }
132 | }
133 | }
134 |
135 | stage('Install') {
136 | steps {
137 | // Perform the install
138 | sh 'npm install'
139 | }
140 | }
141 |
142 | stage('Lint & build') {
143 | when {
144 | // Don't do this when preparing for a release
145 | not { environment name: 'PREPARE', value: 'true' }
146 | }
147 | parallel {
148 | stage('Lint') {
149 | steps {
150 | sh 'npm run lint:ts'
151 | }
152 | }
153 |
154 | stage('Build') {
155 | steps {
156 | sh 'npm run build'
157 | }
158 | }
159 |
160 | stage('Docs') {
161 | steps {
162 | sh 'npm run docs'
163 | }
164 | }
165 | }
166 | }
167 |
168 | stage('Pre-package tests') {
169 | when {
170 | // Don't do this when preparing for a release
171 | not { environment name: 'PREPARE', value: 'true' }
172 | }
173 | parallel {
174 | stage('Unit tests') {
175 | steps {
176 | sh 'MOCHA_FILE=pre-unit-test-results.xml npm run test:ci -- src/**/*.test.tsx'
177 | }
178 | }
179 | stage('Environment tests') {
180 | steps {
181 | sh 'MOCHA_FILE=pre-environments-test-results.xml npm run test:ci -- integration-tests/environments.test.ts'
182 | }
183 | }
184 | }
185 | post {
186 | always {
187 | junit(
188 | allowEmptyResults: true,
189 | keepLongStdio: true,
190 | testResults: 'pre-*-test-results.xml'
191 | )
192 | }
193 | }
194 | }
195 |
196 | // Simple packaging for PRs and runs that don't prepare for releases
197 | stage('Package') {
198 | when {
199 | // Don't do this when preparing for a release
200 | not { environment name: 'PREPARE', value: 'true' }
201 | }
202 | steps {
203 | script {
204 | if (TAG_NAME && TAG_NAME.startsWith("v")) {
205 | // Update the build display name
206 | currentBuild.displayName += ": ${TAG_NAME} (publish)"
207 | } else {
208 | // Change the version to a prerelease if it's not preparing or is a release
209 | changeVersion "${JOB_BASE_NAME}-${BUILD_NUMBER}"
210 | }
211 | }
212 | // Package and archive the archive
213 | script {
214 | packAndArchive()
215 | }
216 | }
217 | }
218 |
219 | stage('Post-packaging tests') {
220 | when {
221 | // Don't do this when preparing for a release
222 | not { environment name: 'PREPARE', value: 'true' }
223 | }
224 | parallel {
225 | stage('Example #1') {
226 | steps {
227 | script { testExampleApp('initializer-and-query') }
228 | }
229 | }
230 | stage('Example #2') {
231 | steps {
232 | script { testExampleApp('multiple-realms') }
233 | }
234 | }
235 | stage('Example #3') {
236 | steps {
237 | script { testExampleApp('simple-context') }
238 | }
239 | }
240 | stage('Example #4') {
241 | steps {
242 | script { testExampleApp('simple-render-props') }
243 | }
244 | }
245 | }
246 | }
247 |
248 | // More advanced packaging for commits tagged as versions
249 | stage('Publish') {
250 | when {
251 | // Don't do this when preparing for a release
252 | not { environment name: 'PREPARE', value: 'true' }
253 | // Check if a tag starting with a v (for version) is pointing at this commit
254 | tag "v*"
255 | }
256 | steps {
257 | // Upload artifacts to GitHub and publish release
258 | withCredentials([
259 | string(credentialsId: 'github-release-token', variable: 'GITHUB_TOKEN')
260 | ]) {
261 | script {
262 | for (file in findFiles(glob: 'react-realm-context-*.tgz')) {
263 | sh "node scripts/github-releases upload-asset $TAG_NAME $file"
264 | }
265 | }
266 | script {
267 | sh "node scripts/github-releases publish $TAG_NAME"
268 | }
269 | }
270 | // Update the gh-pages branch
271 | sshagent(['realm-ci-ssh']) {
272 | sh "npx gh-pages -d generated-docs"
273 | }
274 | // Publish archive to NPM
275 | withCredentials([
276 | file(
277 | credentialsId: 'npm-registry-npmrc',
278 | variable: 'NPM_CONFIG_USERCONFIG',
279 | )
280 | ]) {
281 | sh 'npm publish react-realm-context-*.tgz'
282 | }
283 | }
284 | }
285 |
286 | // Prepares for a release by
287 | // 1. changing version,
288 | // 2. copying release notes to the changelog,
289 | // 3. creating a draft GitHub release and
290 | // 4. pushing a tagged commit to git
291 | stage('Prepare') {
292 | when {
293 | environment name: 'PREPARE', value: 'true'
294 | }
295 | steps {
296 | script {
297 | // Read the current version of the package
298 | packageJson = readJSON file: 'package.json'
299 | versionBefore = "v${packageJson.version}"
300 | // Change the version
301 | nextVersion = changeVersion()
302 | // Add to the displa name of the build job that we're preparing a release
303 | currentBuild.displayName += " (prepare)"
304 | }
305 | // Append the RELEASENOTES to the CHANGELOG
306 | script {
307 | copyReleaseNotes(versionBefore, nextVersion)
308 | }
309 |
310 | // Create a draft release on GitHub
311 | script {
312 | withCredentials([
313 | string(credentialsId: 'github-release-token', variable: 'GITHUB_TOKEN')
314 | ]) {
315 | sh "node scripts/github-releases create-draft $nextVersion RELEASENOTES.md"
316 | }
317 | }
318 |
319 | // Restore the release notes from the template
320 | sh 'cp docs/RELEASENOTES.template.md RELEASENOTES.md'
321 |
322 | // Stage the updates to the files, commit and tag the commit
323 | sh 'git add package.json package-lock.json CHANGELOG.md RELEASENOTES.md'
324 | sh "git commit -m 'Prepare version ${nextVersion}'"
325 | sh "git tag -f ${nextVersion}"
326 |
327 | // Push to GitHub with tags
328 | sshagent(['realm-ci-ssh']) {
329 | sh "git push --tags origin HEAD"
330 | }
331 | }
332 | }
333 | }
334 | }
335 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright 2018 Realm Inc.
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # The `react-realm-context` package has been deprecated
2 |
3 | Please use the [@realm/react](https://www.npmjs.com/package/@realm/react) for a "more React'y experience of Realm".
4 |
--------------------------------------------------------------------------------
/RELEASENOTES.md:
--------------------------------------------------------------------------------
1 | [Changes since {PREVIOUS_VERSION}](https://github.com/realm/react-realm-context/compare/{PREVIOUS_VERSION}...{CURRENT_VERSION})
2 |
3 | ## Enhancements
4 |
5 | * None
6 |
7 | ## Fixed
8 |
9 | * None
10 |
11 | ## Internals
12 |
13 | * None
14 |
--------------------------------------------------------------------------------
/config/mocha.opts:
--------------------------------------------------------------------------------
1 | --require ts-node/register
2 | --require src/test-utils/segfault-handler.ts
3 | --require src/test-utils/realm-js-logging.ts
4 | --forbid-only
5 | --watch-extensions ts,tsx
6 | --exit
7 |
8 |
--------------------------------------------------------------------------------
/config/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../build"
5 | },
6 | "files": [
7 | "../src/index.tsx"
8 | ],
9 | "include": [
10 | "../src/**/*.tsx"
11 | ],
12 | "exclude": [
13 | "../src/**/*.test.tsx"
14 | ],
15 | "typedocOptions": {
16 | "mode": "file",
17 | "target": "ES6",
18 | "out": "generated-docs",
19 | "name": "React Realm Context",
20 | "readme": "none",
21 | "hideGenerator": true,
22 | "preserveConstEnums": true,
23 | "stripInternal": true
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/docs/RELEASENOTES.template.md:
--------------------------------------------------------------------------------
1 | [Changes since {PREVIOUS_VERSION}](https://github.com/realm/react-realm-context/compare/{PREVIOUS_VERSION}...{CURRENT_VERSION})
2 |
3 | ## Enhancements
4 |
5 | * None
6 |
7 | ## Fixed
8 |
9 | * None
10 |
11 | ## Internals
12 |
13 | * None
14 |
--------------------------------------------------------------------------------
/docs/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/realm/react-realm-context/e765ada7bfb70cb84e4e0fb5b46f7d7bb69dd9fb/docs/logo.png
--------------------------------------------------------------------------------
/docs/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
24 |
--------------------------------------------------------------------------------
/examples/.gitignore:
--------------------------------------------------------------------------------
1 | react-realm-context-*.tgz
2 |
--------------------------------------------------------------------------------
/examples/initializer-and-query/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
--------------------------------------------------------------------------------
/examples/initializer-and-query/README.md:
--------------------------------------------------------------------------------
1 | # React Realm Context Example: Multiple Realms
2 |
3 | This example shows how to use the `createRealmContext` API to open and access
4 | two Realms at the same time.
5 |
6 | To run the example, change directory into this folder, install the dependencies
7 |
8 | npm install
9 |
10 | and run the test
11 |
12 | npm test
13 |
14 | ---
15 |
16 | This project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app).
17 |
--------------------------------------------------------------------------------
/examples/initializer-and-query/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "initializer-and-query-example",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "react": "^16.3.2",
7 | "react-dom": "^16.3.2",
8 | "react-realm-context": "^0.1.0",
9 | "react-scripts": "1.1.4",
10 | "realm": "^2.15.0"
11 | },
12 | "scripts": {
13 | "start": "react-scripts start",
14 | "build": "react-scripts build",
15 | "test": "react-scripts test --env=jsdom",
16 | "eject": "react-scripts eject"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/examples/initializer-and-query/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { RealmProvider, RealmConsumer, RealmInitializer } from 'react-realm-context';
3 |
4 | import { SomeDeeplyNestedComponent } from './SomeDeeplyNestedComponent';
5 |
6 | export const App = () => (
7 |
8 |
9 | {({ realm }) => {
10 | realm.create('Person', { name: 'John Doe' });
11 | }}
12 |
13 |
14 |
15 | );
16 |
--------------------------------------------------------------------------------
/examples/initializer-and-query/src/App.test.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert';
2 | import React from 'react';
3 | import ReactDOM from 'react-dom';
4 | import Realm from 'realm';
5 |
6 | import { App } from './App';
7 |
8 | afterAll(() => {
9 | Realm.deleteFile({});
10 | });
11 |
12 | it('renders without crashing', () => {
13 | const div = document.createElement('div');
14 | ReactDOM.render(, div);
15 | assert.equal(div.innerHTML, 'John Doe');
16 | ReactDOM.unmountComponentAtNode(div);
17 | });
18 |
--------------------------------------------------------------------------------
/examples/initializer-and-query/src/SomeDeeplyNestedComponent.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { RealmQuery } from 'react-realm-context';
3 |
4 | export const SomeDeeplyNestedComponent = () => (
5 |
6 | {({ results }) => results.map(person => person.name).join(', ')}
7 |
8 | );
9 |
--------------------------------------------------------------------------------
/examples/multiple-realms/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
--------------------------------------------------------------------------------
/examples/multiple-realms/README.md:
--------------------------------------------------------------------------------
1 | # React Realm Context Example: Multiple Realms
2 |
3 | This example shows how to use the `createRealmContext` API to open and access
4 | two Realms at the same time.
5 |
6 | To run the example, change directory into this folder, install the dependencies
7 |
8 | npm install
9 |
10 | and run the test
11 |
12 | npm test
13 |
14 | ---
15 |
16 | This project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app).
17 |
--------------------------------------------------------------------------------
/examples/multiple-realms/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "multiple-realms-example",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "react": "^16.3.2",
7 | "react-dom": "^16.3.2",
8 | "react-realm-context": "^0.1.0",
9 | "react-scripts": "1.1.4",
10 | "realm": "^2.15.0"
11 | },
12 | "scripts": {
13 | "start": "react-scripts start",
14 | "build": "react-scripts build",
15 | "test": "react-scripts test --env=jsdom",
16 | "eject": "react-scripts eject"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/examples/multiple-realms/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | import { CarsRealmProvider } from './CarsRealm';
4 | import { PersonsRealmProvider } from './PersonsRealm';
5 | import { SomeChildComponent } from './SomeChildComponent';
6 |
7 | class App extends Component {
8 | render() {
9 | return (
10 |
11 |
12 |
13 |
14 |
15 | );
16 | }
17 | }
18 |
19 | export default App;
20 |
--------------------------------------------------------------------------------
/examples/multiple-realms/src/App.test.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert';
2 | import React from 'react';
3 | import ReactDOM from 'react-dom';
4 | import Realm from 'realm';
5 |
6 | import App from './App';
7 |
8 | afterAll(() => {
9 | Realm.deleteFile({ path: 'cars.realm' });
10 | Realm.deleteFile({ path: 'persons.realm' });
11 | });
12 |
13 | it('renders without crashing', () => {
14 | const div = document.createElement('div');
15 | ReactDOM.render(, div);
16 | assert.equal(div.innerHTML, 'true true');
17 | ReactDOM.unmountComponentAtNode(div);
18 | });
19 |
--------------------------------------------------------------------------------
/examples/multiple-realms/src/CarsRealm.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { createRealmContext } from 'react-realm-context';
3 |
4 | const { RealmProvider, RealmConsumer } = createRealmContext();
5 |
6 | const CarSchema = {
7 | name: 'Car',
8 | properties: {
9 | model: 'string',
10 | doors: 'int'
11 | }
12 | };
13 |
14 | const CarsRealmProvider = ({ children }) => (
15 |
20 | );
21 |
22 | export {
23 | CarsRealmProvider,
24 | RealmConsumer as CarsRealmConsumer,
25 | };
26 |
--------------------------------------------------------------------------------
/examples/multiple-realms/src/PersonsRealm.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { createRealmContext } from 'react-realm-context';
3 |
4 | const { RealmProvider, RealmConsumer } = createRealmContext();
5 |
6 | const PersonSchema = {
7 | name: 'Person',
8 | properties: {
9 | name: 'string',
10 | age: 'int'
11 | }
12 | };
13 |
14 | const PersonsRealmProvider = ({ children }) => (
15 |
20 | );
21 |
22 | export {
23 | PersonsRealmProvider,
24 | RealmConsumer as PersonsRealmConsumer,
25 | };
26 |
--------------------------------------------------------------------------------
/examples/multiple-realms/src/SomeChildComponent.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { CarsRealmConsumer } from './CarsRealm';
4 | import { PersonsRealmConsumer } from './PersonsRealm';
5 |
6 | export const SomeChildComponent = () => (
7 |
8 | {({ realm: carRealm }) => (
9 |
10 | {({ realm: personRealm }) => {
11 | const isCarsRealmReady = !carRealm.isClosed && carRealm.empty;
12 | const isPersonsRealmReady = !personRealm.isClosed && personRealm.empty;
13 | return `${isPersonsRealmReady} ${isCarsRealmReady}`;
14 | }}
15 |
16 | )}
17 |
18 | );
19 |
--------------------------------------------------------------------------------
/examples/simple-context/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
--------------------------------------------------------------------------------
/examples/simple-context/README.md:
--------------------------------------------------------------------------------
1 | # React Realm Context Example: Multiple Realms
2 |
3 | This example shows how to use the `createRealmContext` API to open and access
4 | two Realms at the same time.
5 |
6 | To run the example, change directory into this folder, install the dependencies
7 |
8 | npm install
9 |
10 | and run the test
11 |
12 | npm test
13 |
14 | ---
15 |
16 | This project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app).
17 |
--------------------------------------------------------------------------------
/examples/simple-context/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "simple-context-example",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "react": "^16.3.2",
7 | "react-dom": "^16.3.2",
8 | "react-realm-context": "^0.1.0",
9 | "react-scripts": "1.1.4",
10 | "realm": "^2.15.0"
11 | },
12 | "scripts": {
13 | "start": "react-scripts start",
14 | "build": "react-scripts build",
15 | "test": "react-scripts test --env=jsdom",
16 | "eject": "react-scripts eject"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/examples/simple-context/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { RealmProvider } from 'react-realm-context';
3 |
4 | import { SomeDeeplyNestedComponent } from './SomeDeeplyNestedComponent';
5 |
6 | export const App = () => (
7 |
8 |
9 |
10 | );
11 |
--------------------------------------------------------------------------------
/examples/simple-context/src/App.test.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert';
2 | import React from 'react';
3 | import ReactDOM from 'react-dom';
4 | import Realm from 'realm';
5 |
6 | import { App } from './App';
7 |
8 | afterAll(() => {
9 | Realm.deleteFile({});
10 | });
11 |
12 | it('renders without crashing', () => {
13 | const div = document.createElement('div');
14 | ReactDOM.render(, div);
15 | assert.equal(div.innerHTML, 'John Doe');
16 | ReactDOM.unmountComponentAtNode(div);
17 | });
18 |
--------------------------------------------------------------------------------
/examples/simple-context/src/SomeDeeplyNestedComponent.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { RealmConsumer } from 'react-realm-context';
3 |
4 | export const SomeDeeplyNestedComponent = () => (
5 |
6 | {({ realm }) => {
7 | if (realm.empty) {
8 | realm.write(() => {
9 | realm.create('Person', { name: 'John Doe' });
10 | });
11 | }
12 | return realm.objects('Person').map(person => person.name).join(', ');
13 | }}
14 |
15 | );
16 |
--------------------------------------------------------------------------------
/examples/simple-render-props/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
--------------------------------------------------------------------------------
/examples/simple-render-props/README.md:
--------------------------------------------------------------------------------
1 | # React Realm Context Example: Multiple Realms
2 |
3 | This example shows how to use the `createRealmContext` API to open and access
4 | two Realms at the same time.
5 |
6 | To run the example, change directory into this folder, install the dependencies
7 |
8 | npm install
9 |
10 | and run the test
11 |
12 | npm test
13 |
14 | ---
15 |
16 | This project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app).
17 |
--------------------------------------------------------------------------------
/examples/simple-render-props/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "simple-render-props-example",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "react": "^16.3.2",
7 | "react-dom": "^16.3.2",
8 | "react-realm-context": "^0.1.0",
9 | "react-scripts": "1.1.4",
10 | "realm": "^2.15.0"
11 | },
12 | "scripts": {
13 | "start": "react-scripts start",
14 | "build": "react-scripts build",
15 | "test": "react-scripts test --env=jsdom",
16 | "eject": "react-scripts eject"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/examples/simple-render-props/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { RealmProvider } from 'react-realm-context';
3 |
4 | export const App = () => (
5 |
6 | {({ realm }) => {
7 | if (realm.empty) {
8 | realm.write(() => {
9 | realm.create('Person', { name: 'John Doe' });
10 | });
11 | }
12 | return realm.objects('Person').map(person => person.name).join(', ');
13 | }}
14 |
15 | );
16 |
--------------------------------------------------------------------------------
/examples/simple-render-props/src/App.test.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert';
2 | import React from 'react';
3 | import ReactDOM from 'react-dom';
4 | import Realm from 'realm';
5 |
6 | import { App } from './App';
7 |
8 | afterAll(() => {
9 | Realm.deleteFile({});
10 | });
11 |
12 | it('renders without crashing', () => {
13 | const div = document.createElement('div');
14 | ReactDOM.render(, div);
15 | assert.equal(div.innerHTML, 'John Doe');
16 | ReactDOM.unmountComponentAtNode(div);
17 | });
18 |
--------------------------------------------------------------------------------
/integration-tests/environment.ts:
--------------------------------------------------------------------------------
1 | import * as cp from 'child_process';
2 | import * as fs from 'fs-extra';
3 | import * as path from 'path';
4 |
5 | const ENVIRONMENTS_PATH = path.resolve(__dirname, 'environments');
6 | const PROJECT_PATH = path.resolve(__dirname, '..');
7 |
8 | /**
9 | * Specific versions of the components in an environment.
10 | */
11 | export interface IEnvironmentVersions {
12 | realm: string;
13 | react: string;
14 | }
15 |
16 | const getEnvironmentPath = (versions: IEnvironmentVersions) => {
17 | return path.resolve(
18 | ENVIRONMENTS_PATH,
19 | [`realm-${versions.realm}`, `react-${versions.react}`].join('-'),
20 | );
21 | };
22 |
23 | const ensureLinkIntoEnvironment = (environmentPath: string, p: string) => {
24 | fs.ensureSymlinkSync(
25 | path.resolve(PROJECT_PATH, p),
26 | path.resolve(environmentPath, p),
27 | );
28 | };
29 |
30 | /**
31 | * Create a wrapper for a specific environment, including functions to execute in it, remove it and ensure it exists.
32 | */
33 | export const environment = (versions: IEnvironmentVersions) => {
34 | const environmentPath = getEnvironmentPath(versions);
35 |
36 | const exec = (command: string, options?: cp.ExecOptions) => {
37 | cp.execSync(command, {
38 | ...options,
39 | encoding: 'buffer',
40 | cwd: environmentPath,
41 | shell: 'bash',
42 | stdio: ['ignore', 'inherit', 'inherit'],
43 | });
44 | };
45 |
46 | const remove = () => {
47 | if (!fs.existsSync(environmentPath)) {
48 | throw new Error(
49 | `Failed to delete non-existing environment: ${JSON.stringify(
50 | versions,
51 | )}`,
52 | );
53 | } else {
54 | // Delete the directory
55 | fs.removeSync(environmentPath);
56 | }
57 | };
58 |
59 | const ensure = () => {
60 | // Create environment path if its not already created
61 | fs.ensureDirSync(environmentPath);
62 |
63 | // Create a symbolic link of specific files into the environment
64 | ensureLinkIntoEnvironment(environmentPath, 'package.json');
65 | ensureLinkIntoEnvironment(environmentPath, 'tsconfig.json');
66 | ensureLinkIntoEnvironment(environmentPath, 'config');
67 | ensureLinkIntoEnvironment(environmentPath, 'src');
68 | // Copy the package-lock to ensure versions are locked down but it doesn't get mutated
69 | fs.copyFileSync(
70 | path.resolve(PROJECT_PATH, 'package-lock.json'),
71 | path.resolve(environmentPath, 'package-lock.json'),
72 | );
73 |
74 | if (!fs.existsSync(path.resolve(environmentPath, 'node_modules'))) {
75 | // Install the specific versions of Realm and React
76 | const dependencies = [
77 | `realm@${versions.realm}`,
78 | `react@${versions.react}`,
79 | ].join(' ');
80 | exec(`npm install ${dependencies} --no-save --fallback-to-build=false`);
81 | }
82 | };
83 |
84 | return { ensure, remove, exec };
85 | };
86 |
--------------------------------------------------------------------------------
/integration-tests/environments.test.ts:
--------------------------------------------------------------------------------
1 | import { environment, IEnvironmentVersions } from './environment';
2 |
3 | // The earliest supported version could be 2.16.0 (if we only ever tested with node 8).
4 | // (technically 2.15.0, but that doesn't have TypeScript types for the connection state listener API).
5 | // But ... the ealiest version with prebuilt binaries for node 10 is 2.19.0
6 | // 2.19.0 is also the first version which fixes the types for Realm.Sync.User.login, which the test-harness relies on.
7 | // but ... 2.20.0 is the first version to add `uploadAllLocalChanges` which some ROS related tests use.
8 | // 2.22.0 adds support for relative urls when calling `Realm.Sync.User.createConfiguration()` which the ROS related
9 | // related tests needs.
10 | const VERSIONS = {
11 | realm: [process.env.REALM_OBJECT_SERVER_URL ? '2.22.0' : '2.20.0', 'latest'],
12 | react: ['16.3.2', 'latest'],
13 | };
14 |
15 | const versionPermutations = () => {
16 | const result: IEnvironmentVersions[] = [];
17 | for (const realmVersion of VERSIONS.realm) {
18 | for (const reactVersion of VERSIONS.react) {
19 | result.push({
20 | realm: realmVersion,
21 | react: reactVersion,
22 | });
23 | }
24 | }
25 | return result;
26 | };
27 |
28 | describe('Environments', () => {
29 | const allVersions = versionPermutations();
30 | for (const versions of allVersions) {
31 | describe(
32 | [`Realm (${versions.realm})`, `React (${versions.react})`].join(', '),
33 | () => {
34 | const env = environment(versions);
35 |
36 | before(function() {
37 | // It might take some time to install the environment
38 | this.timeout(60000);
39 | env.ensure();
40 | });
41 |
42 | after(() => {
43 | if (process.env.CLEAN_ENVIRONMENT_AFTER_TEST) {
44 | env.remove();
45 | }
46 | });
47 |
48 | it('creates an environment and passes the test', function() {
49 | this.timeout(20000);
50 | // Run the tests with the appropreate arguments
51 | const args = [
52 | // We need to tell node (via mocha) to preserve the symlinks when requiring modules,
53 | // otherwise we'll be requiring modules relative to the original src folder
54 | '--preserve-symlinks',
55 | '--reporter dot',
56 | ];
57 | return env.exec(`npm test -- ${args.join(' ')}`);
58 | });
59 | },
60 | );
61 | }
62 | });
63 |
--------------------------------------------------------------------------------
/integration-tests/examples.test.ts:
--------------------------------------------------------------------------------
1 | import * as cp from 'child_process';
2 | import * as fs from 'fs-extra';
3 | import * as path from 'path';
4 |
5 | const EXAMPLES_PATH = path.resolve(__dirname, '../examples');
6 |
7 | describe('Examples', () => {
8 | const examples = fs.readdirSync(EXAMPLES_PATH);
9 | for (const example of examples) {
10 | const examplePath = path.resolve(EXAMPLES_PATH, example);
11 | const stat = fs.statSync(examplePath);
12 | if (stat.isDirectory()) {
13 | describe(example, function() {
14 | // Increase the timeout to allow for NPM installation
15 | this.timeout(60000);
16 | it('installs and passes its test', () => {
17 | // NPM install with the packaged version of react-realm-context and test
18 | // CI=true to prevent jest interactive mode
19 | cp.execSync(
20 | 'npm install ../../react-realm-context-*.tgz --no-save && CI=true npm test -- --forceExit',
21 | {
22 | cwd: examplePath,
23 | encoding: 'utf8',
24 | stdio: ['ignore', 'inherit', 'inherit'],
25 | },
26 | );
27 | });
28 | });
29 | }
30 | }
31 | });
32 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-realm-context",
3 | "version": "0.3.0",
4 | "description": "Components that simplifies using Realm with React",
5 | "scripts": {
6 | "build": "tsc -p config/tsconfig.build.json",
7 | "docs": "typedoc --tsconfig config/tsconfig.build.json",
8 | "fix-headers": "node ./scripts/fix-headers.js",
9 | "lint:ts": "tslint --project tsconfig.json",
10 | "prepack": "npm run build",
11 | "start": "mocha --opts config/mocha.opts -w",
12 | "test": "mocha --opts config/mocha.opts src/**/*.test.tsx",
13 | "test:ci": "mocha --opts config/mocha.opts --reporter mocha-junit-reporter",
14 | "test:environments": "mocha --opts config/mocha.opts integration-tests/environments.test.ts",
15 | "pretest:examples": "npm pack",
16 | "test:examples": "mocha --opts config/mocha.opts integration-tests/examples.test.ts"
17 | },
18 | "main": "build/index.js",
19 | "types": "build/index.d.ts",
20 | "author": {
21 | "name": "Realm",
22 | "email": "help@realm.io",
23 | "url": "https://realm.io"
24 | },
25 | "repository": "https://github.com/realm/react-realm-context",
26 | "license": "Apache-2.0",
27 | "devDependencies": {
28 | "@octokit/rest": "^16.2.0",
29 | "@types/debug": "^4.1.2",
30 | "@types/fs-extra": "^5.0.2",
31 | "@types/mocha": "^5.2.0",
32 | "@types/node": "^11.9.5",
33 | "@types/react": "^16.3.14",
34 | "@types/react-test-renderer": "^16.0.1",
35 | "@types/uuid": "^3.4.4",
36 | "commander": "^2.19.0",
37 | "fs-extra": "^7.0.1",
38 | "gh-pages": "^2.0.1",
39 | "mocha": "^6.0.2",
40 | "mocha-junit-reporter": "^1.17.0",
41 | "prettier": "^1.12.1",
42 | "react": "^16.3.2",
43 | "react-test-renderer": "^16.3.2",
44 | "realm": "2.22.0",
45 | "segfault-handler": "^1.0.1",
46 | "ts-node": "^8.0.2",
47 | "tslint": "^5.11.0",
48 | "tslint-config-prettier": "^1.17.0",
49 | "tslint-eslint-rules": "^5.4.0",
50 | "tslint-plugin-prettier": "^2.0.1",
51 | "tslint-react": "^3.6.0",
52 | "typedoc": "^0.14.2",
53 | "typescript": "^3.1.6",
54 | "uuid": "^3.3.2"
55 | },
56 | "peerDependencies": {
57 | "react": ">=16.3",
58 | "realm": ">=2.20"
59 | },
60 | "dependencies": {
61 | "debug": "^4.1.1",
62 | "fast-deep-equal": "^2.0.1",
63 | "memoize-one": "^5.0.0"
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/scripts/fix-headers.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const { resolve, extname } = require('path');
3 |
4 | const EXTENSIONS_WITH_HEADERS = ['.ts', '.tsx'];
5 |
6 | function readHeader() {
7 | const headerPath = resolve(__dirname, 'header.txt');
8 | const header = fs.readFileSync(headerPath, 'utf8');
9 | // Split up the header to identify the first and last lines
10 | const lines = header.trim().split('\n');
11 | const firstLine = lines[0];
12 | const lastLine = lines[lines.length - 1];
13 | return {
14 | lines,
15 | firstLine,
16 | lastLine,
17 | };
18 | }
19 |
20 | const header = readHeader();
21 |
22 | function processDirectory(path) {
23 | const subPaths = fs.readdirSync(path);
24 | for (const subPath of subPaths) {
25 | const absoluteSubPath = resolve(path, subPath);
26 | const stat = fs.statSync(absoluteSubPath);
27 | if (stat.isDirectory()) {
28 | processDirectory(absoluteSubPath);
29 | } else if (stat.isFile()) {
30 | processFile(absoluteSubPath);
31 | }
32 | }
33 | }
34 |
35 | function processFile(path) {
36 | const extension = extname(path);
37 | if (EXTENSIONS_WITH_HEADERS.indexOf(extension) > -1) {
38 | const contents = fs.readFileSync(path, 'utf8');
39 | const newContents = processFileContents(contents);
40 | fs.writeFileSync(path, newContents);
41 | }
42 | }
43 |
44 | function processFileContents(contents) {
45 | const lines = contents.split('\n');
46 | const headerLines = locateHeader(lines);
47 | if (headerLines.start === -1 || headerLines.end === -1) {
48 | // Prepend the header to the file contents
49 | return [...header.lines, '', ...lines].join('\n');
50 | } else {
51 | // Modify the lines to add the latest header
52 | lines.splice(
53 | headerLines.start,
54 | headerLines.end + 1,
55 | ...header.lines
56 | ).join('\n');
57 | // Return the new lines
58 | return lines.join('\n');
59 | }
60 | }
61 |
62 | function locateHeader(lines) {
63 | return {
64 | start: lines.indexOf(header.firstLine),
65 | end: lines.lastIndexOf(header.lastLine),
66 | };
67 | }
68 |
69 | if(process.argv.length === 2) {
70 | const srcPath = resolve(__dirname, '../src');
71 | processDirectory(srcPath);
72 | } else {
73 | // The script was asked to run on specific file(s)
74 | const [_node, _script, ...paths] = process.argv;
75 | for (const path of paths) {
76 | processFile(path);
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/scripts/github-releases.js:
--------------------------------------------------------------------------------
1 | const assert = require("assert");
2 | const program = require("commander");
3 | const octokit = require("@octokit/rest")();
4 | const path = require("path");
5 | const fs = require("fs");
6 |
7 | const {
8 | GITHUB_TOKEN,
9 | GITHUB_OWNER,
10 | GITHUB_REPO
11 | } = process.env;
12 |
13 | assert(GITHUB_TOKEN, "Expected a GITHUB_TOKEN environment variable");
14 | assert(GITHUB_OWNER, "Expected a GITHUB_OWNER environment variable");
15 | assert(GITHUB_REPO, "Expected a GITHUB_REPO environment variable");
16 |
17 | // Authenticate
18 | octokit.authenticate({
19 | type: "token",
20 | token: GITHUB_TOKEN
21 | });
22 |
23 | /**
24 | * Wraps an action callback with something that prints errors before exiting with an appropriate status code
25 | */
26 | function wrap(callback) {
27 | return (...args) => {
28 | callback(...args).then(undefined, err => {
29 | console.error(err.stack);
30 | process.exit(1);
31 | });
32 | };
33 | }
34 |
35 | function determinContentType(assetPath) {
36 | const ext = path.extname(assetPath);
37 | if (ext === ".tgz") {
38 | return "application/tar+gzip"
39 | } else {
40 | throw new Error(`Unable to determine content type of ${ext} files`);
41 | }
42 | }
43 |
44 | program
45 | .command("upload-asset ")
46 | .action(wrap(async (tag, assetPath) => {
47 | // Request all releases, as we cannot request by tag name for draft releases
48 | const { data: releases } = await octokit.repos.listReleases({
49 | owner: GITHUB_OWNER,
50 | repo: GITHUB_REPO,
51 | });
52 | // Find the release with the right tag name
53 | const release = releases.find(({ tag_name }) => tag_name === tag);
54 | if (release) {
55 | const assetContentType = determinContentType(assetPath);
56 | const assetContent = fs.readFileSync(assetPath);
57 | await octokit.repos.uploadReleaseAsset({
58 | headers: {
59 | "content-length": assetContent.length,
60 | "content-type": assetContentType
61 | },
62 | url: release.upload_url,
63 | name: path.basename(assetPath),
64 | file: assetContent,
65 | });
66 | } else {
67 | throw new Error("Couldn't find the release");
68 | }
69 | }));
70 |
71 | program
72 | .command("create-draft ")
73 | .action(wrap(async (tag, releaseNotesPath) => {
74 | // Read the content of the release notes
75 | const releaseNotes = fs.readFileSync(releaseNotesPath, 'utf8');
76 | // Create a draft release
77 | await octokit.repos.createRelease({
78 | owner: GITHUB_OWNER,
79 | repo: GITHUB_REPO,
80 | tag_name: tag,
81 | name: `${tag.substring(1)}: ...`,
82 | body: releaseNotes,
83 | draft: true
84 | });
85 | }));
86 |
87 | program
88 | .command("publish ")
89 | .action(wrap(async (tag, releaseNotesPath) => {
90 | // Request all releases, as we cannot request by tag name for draft releases
91 | const { data: releases } = await octokit.repos.listReleases({
92 | owner: GITHUB_OWNER,
93 | repo: GITHUB_REPO,
94 | });
95 | // Find the release with the right tag name
96 | const release = releases.find(({ tag_name }) => tag_name === tag);
97 | if (release) {
98 | await octokit.repos.updateRelease({
99 | owner: GITHUB_OWNER,
100 | repo: GITHUB_REPO,
101 | release_id: release.id,
102 | draft: false,
103 | });
104 | } else {
105 | throw new Error("Couldn't find the release");
106 | }
107 | }));
108 |
109 | program.parse(process.argv);
110 |
--------------------------------------------------------------------------------
/scripts/header.txt:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Copyright 2018 Realm Inc.
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 | //
17 | ////////////////////////////////////////////////////////////////////////////
18 |
--------------------------------------------------------------------------------
/scripts/next-version.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 | const cp = require('child_process');
4 |
5 | const releaseNotesPath = path.resolve(__dirname, '../RELEASENOTES.md');
6 | const releaseNotes = fs.readFileSync(releaseNotesPath, 'utf8');
7 |
8 | const hasBreakingChanges = /## Breaking Changes/.test(releaseNotes);
9 | const hasNoEnhancements = /## Enhancements\n\n\* None/.test(releaseNotes);
10 | if (hasBreakingChanges) {
11 | console.log('major');
12 | } else if (hasNoEnhancements) {
13 | console.log('patch');
14 | } else {
15 | console.log('minor');
16 | }
17 |
--------------------------------------------------------------------------------
/src/@types/memoize-one/index.d.ts:
--------------------------------------------------------------------------------
1 | // Type definitions for memoize-one 4.1
2 | // Project: https://github.com/alexreardon/memoize-one#readme
3 | // Definitions by: Karol Majewski , Frank Li
4 | // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
5 |
6 | declare module 'memoize-one' {
7 | type EqualityFn = (a: any, b: any, index: number) => boolean;
8 |
9 | function memoizeOne any>(
10 | resultFn: T,
11 | isEqual?: EqualityFn,
12 | ): T;
13 |
14 | namespace memoizeOne {
15 |
16 | }
17 |
18 | export = memoizeOne;
19 | }
20 |
--------------------------------------------------------------------------------
/src/@types/segfault-handler/index.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'segfault-handler' {
2 | export function registerHandler(path: string): void;
3 | }
4 |
--------------------------------------------------------------------------------
/src/RealmConnection.test.tsx:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Copyright 2018 Realm Inc.
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 | //
17 | ////////////////////////////////////////////////////////////////////////////
18 |
19 | import * as assert from 'assert';
20 | import * as React from 'react';
21 | import * as renderer from 'react-test-renderer';
22 |
23 | import { schema } from './test-utils/persons-realm';
24 | import { withROS } from './test-utils/with-ros';
25 |
26 | import { RealmConnection, RealmConsumer, RealmProvider } from '.';
27 |
28 | describe('RealmConnection', () => {
29 | let tree: renderer.ReactTestRenderer;
30 |
31 | afterEach(() => {
32 | // Delete the default file after the tests
33 | Realm.deleteFile({});
34 | // Unmounting should close the Realm
35 | if (tree) {
36 | tree.unmount();
37 | tree = null;
38 | }
39 | });
40 |
41 | it('will remain disconnected for local Realms', () => {
42 | const states: string[] = [];
43 | tree = renderer.create(
44 |
45 |
46 | {connectionState => {
47 | states.push(connectionState);
48 | return connectionState;
49 | }}
50 |
51 | ,
52 | );
53 |
54 | // Asserting the tree matches the string which was returned
55 | assert.equal(tree.toJSON(), 'disconnected');
56 | assert.deepEqual(states, ['disconnected']);
57 | });
58 |
59 | withROS.it('will be connecting for synced Realms', async function() {
60 | const states: string[] = [];
61 | const user = await this.ros.createTestUser();
62 | const config = user.createConfiguration({
63 | schema,
64 | sync: { fullSynchronization: true, url: '~/connection-test' },
65 | });
66 |
67 | let realm: Realm;
68 |
69 | // Wait for the connection state to be connected
70 | await new Promise(resolve => {
71 | // Render with a sync configuration
72 | tree = renderer.create(
73 |
74 |
75 | {context => {
76 | realm = context.realm;
77 | return null;
78 | }}
79 |
80 |
81 | {connectionState => {
82 | states.push(connectionState);
83 | if (connectionState === 'connected') {
84 | resolve();
85 | }
86 | return connectionState;
87 | }}
88 |
89 | ,
90 | );
91 | });
92 | // Assert something about the states that changed so far
93 | assert.deepEqual(states, ['disconnected', 'connecting', 'connected']);
94 | // Create a promise that resolves when the state changes from connected to disconnected
95 | const disconnectedPromise = new Promise((resolve, reject) => {
96 | realm.syncSession.addConnectionNotification((newState, oldState) => {
97 | if (newState === 'disconnected' && oldState === 'connected') {
98 | resolve();
99 | } else {
100 | reject(`Unexpected state change, got ${newState} => ${oldState}`);
101 | }
102 | });
103 | });
104 | // Unmounting should close the Realm and disconnect
105 | tree.unmount();
106 | // Asserting the tree matches the string which was returned
107 | assert.equal(tree.toJSON(), null);
108 | // Forget about the tree
109 | tree = null;
110 | // Await a disconnection
111 | await disconnectedPromise;
112 | });
113 | });
114 |
--------------------------------------------------------------------------------
/src/RealmConnection.tsx:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Copyright 2018 Realm Inc.
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 | //
17 | ////////////////////////////////////////////////////////////////////////////
18 |
19 | import * as React from 'react';
20 | import * as Realm from 'realm';
21 |
22 | import { IRealmContext } from '.';
23 |
24 | interface IRealmConnectionState {
25 | connectionState: Realm.Sync.ConnectionState;
26 | }
27 |
28 | /**
29 | * Props passed to a RealmConnection component.
30 | */
31 | export interface IRealmConnectionProps {
32 | children: (connectionState: Realm.Sync.ConnectionState) => React.ReactChild;
33 | }
34 |
35 | /**
36 | * Generates a RealmConnection wrapping a context consumer.
37 | *
38 | * Use {@link createRealmContext} or the default RealmConnection instead of calling this directly.
39 | */
40 | export const generateRealmConnection = (
41 | WrappedConsumer: React.Consumer,
42 | ): React.ComponentType => {
43 | /**
44 | * Adds a listener to the connection state (using `syncSession.addConnectionNotification`) and renders the function
45 | * passed as children, like a [render prop](https://reactjs.org/docs/render-props.html#using-props-other-than-render).
46 | */
47 | class RealmConnection extends React.Component<
48 | IRealmConnectionProps,
49 | IRealmConnectionState
50 | > {
51 | /**
52 | * The state stores the latest known state of connection, defaults to disconnected.
53 | */
54 | public state: IRealmConnectionState = {
55 | connectionState: Realm.Sync.ConnectionState.Disconnected,
56 | };
57 |
58 | private realm: Realm;
59 |
60 | /**
61 | * Renders the component.
62 | */
63 | public render() {
64 | return {this.renderContext};
65 | }
66 |
67 | public componentWillUnmount() {
68 | this.forgetSyncSession();
69 | }
70 |
71 | private renderContext = (context: IRealmContext) => {
72 | if (context && this.realm !== context.realm) {
73 | this.forgetSyncSession();
74 | // Remember this Realm to avoid adding the notification more than once
75 | this.realm = context.realm;
76 | // Add a connection notification listener
77 | if (this.realm && this.realm.syncSession) {
78 | this.realm.syncSession.addConnectionNotification(
79 | this.onConnectionStateChange,
80 | );
81 | }
82 | }
83 | return this.props.children(this.state.connectionState);
84 | };
85 |
86 | private onConnectionStateChange = (
87 | connectionState: Realm.Sync.ConnectionState,
88 | ) => {
89 | // Unmounting the Provider component will close the Realm and synchroniously call this callback before
90 | // the listener is removed from the session. Therefore we need to check if the session has been removed before
91 | // updating the state
92 | if (this.realm && this.realm.syncSession) {
93 | this.setState({ connectionState });
94 | }
95 | };
96 |
97 | private forgetSyncSession() {
98 | if (this.realm && this.realm.syncSession) {
99 | this.realm.syncSession.removeConnectionNotification(
100 | this.onConnectionStateChange,
101 | );
102 | delete this.realm;
103 | }
104 | }
105 | }
106 | return RealmConnection;
107 | };
108 |
--------------------------------------------------------------------------------
/src/RealmConsumer.test.tsx:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Copyright 2018 Realm Inc.
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 | //
17 | ////////////////////////////////////////////////////////////////////////////
18 |
19 | import * as assert from 'assert';
20 | import * as React from 'react';
21 | import * as renderer from 'react-test-renderer';
22 |
23 | import { RealmConsumer, RealmProvider } from '.';
24 |
25 | describe('RealmConsumer', () => {
26 | let tree: renderer.ReactTestRenderer;
27 |
28 | afterEach(() => {
29 | if (tree) {
30 | tree.unmount();
31 | tree = undefined;
32 | }
33 | // Delete the default file after the tests
34 | Realm.deleteFile({});
35 | });
36 |
37 | it('re-renders when Realm changes only when asked to', async () => {
38 | // Create a context
39 | let defaultRenderCount = 0;
40 | let updatingRenderCount = 0;
41 | // Render it ..
42 | tree = renderer.create(
43 |
46 |
47 | {({ realm }) => {
48 | process.nextTick(() => {
49 | realm.write(() => {
50 | realm.create('Person', { name: 'Alice' });
51 | });
52 | // Update it again ...
53 | process.nextTick(() => {
54 | realm.write(() => {
55 | realm.create('Person', { name: 'Bob' });
56 | });
57 | });
58 | });
59 | defaultRenderCount++;
60 | return null;
61 | }}
62 |
63 |
64 | {({ realm }) => {
65 | updatingRenderCount++;
66 | return realm
67 | .objects<{ name: string }>('Person')
68 | .map(p => p.name)
69 | .join(' & ');
70 | }}
71 |
72 | ,
73 | );
74 |
75 | // Wait for component to re-render
76 | await new Promise(resolve => process.nextTick(resolve));
77 | assert.equal(tree.toJSON(), 'Alice & Bob');
78 | tree.unmount();
79 |
80 | assert.equal(defaultRenderCount, 1);
81 | // Initially, creating Alice and creating Bob
82 | assert.equal(updatingRenderCount, 3);
83 | });
84 | });
85 |
--------------------------------------------------------------------------------
/src/RealmConsumer.tsx:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Copyright 2018 Realm Inc.
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 | //
17 | ////////////////////////////////////////////////////////////////////////////
18 |
19 | import * as React from 'react';
20 |
21 | import { IRealmContext } from '.';
22 |
23 | type ConsumerChild = (context: IRealmContext) => React.ReactNode;
24 |
25 | /**
26 | * Props passed to a RealmConsumer component.
27 | */
28 | export interface IRealmConsumerProps {
29 | children: ConsumerChild;
30 | updateOnChange?: boolean;
31 | }
32 |
33 | /**
34 | * Generates a RealmConsumer wrapping a context consumer.
35 | *
36 | * Use {@link createRealmContext} or the default RealmConsumer instead of calling this directly.
37 | */
38 | export const generateRealmConsumer = (
39 | WrappedConsumer: React.Consumer,
40 | ): React.ComponentType => {
41 | class RealmConsumer extends React.Component {
42 | // TODO: Add propTypes for non-TypeScript users
43 |
44 | private realm: Realm;
45 |
46 | public componentWillUnmount() {
47 | this.forgetRealm();
48 | }
49 |
50 | /**
51 | * Renders the component.
52 | */
53 | public render() {
54 | return {this.renderContext};
55 | }
56 |
57 | private renderContext = (context: IRealmContext | null) => {
58 | const { updateOnChange } = this.props;
59 | // Register a listener when the Realm passed throught the context changes
60 | if (context !== null && this.realm !== context.realm && updateOnChange) {
61 | // Remove the listener from any Realm to which it was already added
62 | this.forgetRealm();
63 | this.realm = context.realm;
64 | this.realm.addListener('change', this.onRealmChange);
65 | this.realm.addListener('schema', this.onRealmChange);
66 | }
67 | // Calling the function passed as children with the context
68 | return this.props.children(context);
69 | };
70 |
71 | private forgetRealm() {
72 | if (this.realm && !this.realm.isClosed) {
73 | this.realm.removeListener('change', this.onRealmChange);
74 | this.realm.removeListener('schema', this.onRealmChange);
75 | }
76 | delete this.realm;
77 | }
78 |
79 | private onRealmChange = () => {
80 | this.forceUpdate();
81 | };
82 | }
83 |
84 | return RealmConsumer;
85 | };
86 |
--------------------------------------------------------------------------------
/src/RealmInitializer.test.tsx:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Copyright 2018 Realm Inc.
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 | //
17 | ////////////////////////////////////////////////////////////////////////////
18 |
19 | import * as assert from 'assert';
20 | import * as React from 'react';
21 | import * as renderer from 'react-test-renderer';
22 |
23 | import { IPerson, schema } from './test-utils/persons-realm';
24 |
25 | import { RealmConsumer, RealmInitializer, RealmProvider } from '.';
26 |
27 | describe('RealmInitializer', () => {
28 | let tree: renderer.ReactTestRenderer;
29 |
30 | afterEach(() => {
31 | // Delete the default file after the tests
32 | Realm.deleteFile({});
33 | });
34 |
35 | it('will initialize the Realm with data', () => {
36 | let realmReference: Realm;
37 |
38 | tree = renderer.create(
39 |
40 |
41 | {({ realm }) => {
42 | // Hang onto the realm for the test
43 | realmReference = realm;
44 | realm.create('Person', { name: 'Bobby Boy' });
45 | }}
46 |
47 |
48 | {({ realm }) => {
49 | return realm
50 | .objects('Person')
51 | .map(person => person.name)
52 | .join(', ');
53 | }}
54 |
55 | ,
56 | );
57 |
58 | // Asserting the tree matches the string which was returned
59 | assert.equal(tree.toJSON(), 'Bobby Boy');
60 | // Unmounting should close the Realm
61 | tree.unmount();
62 | tree = null;
63 | // Check that unmounting did indeed close the Realm
64 | assert.equal(realmReference.isClosed, true, 'the Realm was not closed');
65 | });
66 | });
67 |
--------------------------------------------------------------------------------
/src/RealmInitializer.tsx:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Copyright 2018 Realm Inc.
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 | //
17 | ////////////////////////////////////////////////////////////////////////////
18 |
19 | import * as React from 'react';
20 | import * as Realm from 'realm';
21 |
22 | import { IRealmContext } from '.';
23 |
24 | /**
25 | * Props passed to a RealmInitializer component.
26 | */
27 | export interface IRealmInitializerProps {
28 | children: ({ realm }: { realm: Realm }) => void;
29 | }
30 |
31 | /**
32 | * Generates a RealmInitializer wrapping a context consumer.
33 | *
34 | * Use {@link createRealmContext} or the default RealmInitializer instead of calling this directly.
35 | */
36 | export const generateRealmInitializer = (
37 | WrappedConsumer: React.Consumer,
38 | ) => {
39 | const RealmInitializer = ({ children }: IRealmInitializerProps) => (
40 |
41 | {({ realm }) => {
42 | // If the realm is empty - call the function provided as child to initialize the Realm
43 | if (realm.empty) {
44 | realm.write(() => {
45 | children({ realm });
46 | });
47 | }
48 | return null;
49 | }}
50 |
51 | );
52 | return RealmInitializer;
53 | };
54 |
--------------------------------------------------------------------------------
/src/RealmProgress.test.tsx:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Copyright 2018 Realm Inc.
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 | //
17 | ////////////////////////////////////////////////////////////////////////////
18 |
19 | import * as assert from 'assert';
20 | import * as React from 'react';
21 | import * as renderer from 'react-test-renderer';
22 |
23 | import { schema } from './test-utils/persons-realm';
24 | import { withROS } from './test-utils/with-ros';
25 |
26 | import {
27 | IRealmProgressValue,
28 | RealmConsumer,
29 | RealmInitializer,
30 | RealmProgress,
31 | RealmProvider,
32 | } from '.';
33 |
34 | describe('RealmProgress', () => {
35 | let tree: renderer.ReactTestRenderer;
36 |
37 | afterEach(() => {
38 | // Delete the default file after the tests
39 | Realm.deleteFile({});
40 | // Unmounting should close the Realm
41 | if (tree) {
42 | tree.unmount();
43 | tree = null;
44 | }
45 | });
46 |
47 | it('wont report progress for local Realms', () => {
48 | const states: IRealmProgressValue[] = [];
49 | let realm: Realm;
50 | tree = renderer.create(
51 |
52 |
53 | {context => {
54 | realm = context.realm;
55 | realm.create('Person', { name: 'John' });
56 | }}
57 |
58 |
59 | {progress => {
60 | states.push(progress);
61 | return JSON.stringify(progress.isLoading);
62 | }}
63 |
64 | ,
65 | );
66 |
67 | // Asserting the tree matches the string which was returned
68 | assert.equal(tree.toJSON(), 'false');
69 | // Assert something about the states
70 | assert.equal(states.length, 1);
71 | const firstProgress = states[0];
72 | assert.equal(firstProgress.isLoading, false);
73 | assert.equal(firstProgress.isDownloading, false);
74 | assert.equal(firstProgress.isUploading, false);
75 | // Assert that a person was indeed added
76 | assert.equal(realm.objects('Person').length, 1);
77 | });
78 |
79 | withROS.it('will be progressing for synced Realms', async function() {
80 | const states: IRealmProgressValue[] = [];
81 | const user = await this.ros.createTestUser();
82 | const config = user.createConfiguration({
83 | schema,
84 | sync: { fullSynchronization: true, url: '~/progress-test' },
85 | });
86 |
87 | // Let's first create a Realm that can be downloaded.
88 | const realm = new Realm(config);
89 | realm.write(() => {
90 | realm.create('Person', { name: 'Alice' });
91 | });
92 | await realm.syncSession.uploadAllLocalChanges();
93 | // Close and delete the Realm file to force redownloading it from the server.
94 | realm.close();
95 | Realm.deleteFile(config);
96 |
97 | // Wait for the connection state to be connected
98 | await new Promise(resolve => {
99 | // Render with a sync configuration
100 | tree = renderer.create(
101 |
102 |
103 | {progress => {
104 | states.push(progress);
105 | // console.log(progress);
106 | if (states.length === 1) {
107 | // Create an object to uploads
108 | progress.realm.write(() => {
109 | progress.realm.create('Person', { name: 'Bob' });
110 | });
111 | } else if ('upload' in progress && !progress.isLoading) {
112 | // Assert that persons were indeed added
113 | assert.equal(progress.realm.objects('Person').length, 2);
114 | // Resolve when we're done uploading
115 | resolve();
116 | }
117 | return JSON.stringify(progress.isLoading);
118 | }}
119 |
120 | ,
121 | );
122 | });
123 | // Assert something about the states that changed so far
124 | assert(states.length > 0, 'Expected at least one progress state');
125 | // Assert that the first and last states are not loading
126 | const firstProgress = states[0];
127 | const lastProgress = states[states.length - 1];
128 | assert.equal(firstProgress.isLoading, false);
129 | assert.equal(firstProgress.isDownloading, false);
130 | assert.equal(firstProgress.isUploading, false);
131 | assert.equal(lastProgress.isLoading, false);
132 | assert.equal(lastProgress.isDownloading, false);
133 | assert.equal(lastProgress.isUploading, false);
134 | // Assert that something was downloaded and uploaded
135 | const { download, upload } = lastProgress;
136 | assert(
137 | download && download.transferred > 0,
138 | 'Expected something was downloaded',
139 | );
140 | assert(upload && upload.transferred > 0, 'Expected something was uploaded');
141 | });
142 | });
143 |
--------------------------------------------------------------------------------
/src/RealmProgress.tsx:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Copyright 2018 Realm Inc.
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 | //
17 | ////////////////////////////////////////////////////////////////////////////
18 |
19 | import * as React from 'react';
20 | import * as Realm from 'realm';
21 |
22 | import { IRealmContext } from '.';
23 |
24 | interface IProgress {
25 | transferred: number;
26 | transferable: number;
27 | }
28 |
29 | interface IRealmProgressState {
30 | download?: IProgress;
31 | upload?: IProgress;
32 | }
33 |
34 | /**
35 | * Passed to the render-prop callback provided as `children` to `RealmProgress`.
36 | */
37 | export interface IRealmProgressValue
38 | extends IRealmProgressState,
39 | IRealmContext {
40 | /**
41 | * Is the realm in the progress of either downloading or uploading?
42 | * It's meaning depends on the `direction` provided as prop to `RealmProgress`.
43 | */
44 | isLoading: boolean;
45 |
46 | /**
47 | * This will be true if we're downloading and the transferred are less than the transferable.
48 | */
49 | isDownloading: boolean;
50 |
51 | /**
52 | * This will be true if we're uploading and the transferred are less than the transferable.
53 | */
54 | isUploading: boolean;
55 | }
56 |
57 | /**
58 | * Props passed to a RealmProgress component.
59 | */
60 | export interface IRealmProgressProps {
61 | /**
62 | * In what direction should the callback passed as `children` be called?
63 | * Possible values are "download", "upload" or "both" (default).
64 | */
65 | direction?: Realm.Sync.ProgressDirection | 'both';
66 | /**
67 | * Should the callback passed as `children` be called indefinitly or just for outstanding work which has not been
68 | * uploaded / downloaded since the point of mounting the component?
69 | */
70 | mode?: Realm.Sync.ProgressMode;
71 | children: (value: IRealmProgressValue) => React.ReactChild;
72 | }
73 |
74 | /**
75 | * Generates a RealmProgress wrapping a context consumer.
76 | *
77 | * Use {@link createRealmContext} or the default RealmProgress instead of calling this directly.
78 | */
79 | export const generateRealmProgress = (
80 | WrappedConsumer: React.Consumer,
81 | ): React.ComponentType => {
82 | /**
83 | * Adds a listener to the connection state (using `syncSession.addConnectionNotification`) and renders the function
84 | * passed as children, like a [render prop](https://reactjs.org/docs/render-props.html#using-props-other-than-render).
85 | */
86 | class RealmProgress extends React.Component<
87 | IRealmProgressProps,
88 | IRealmProgressState
89 | > {
90 | /**
91 | * The state stores the latest known state of connection, defaults to disconnected.
92 | */
93 | public state: IRealmProgressState = {};
94 |
95 | private realm: Realm;
96 |
97 | /**
98 | * Renders the component.
99 | */
100 | public render() {
101 | return {this.renderContext};
102 | }
103 |
104 | public componentWillUnmount() {
105 | this.forgetRealm();
106 | }
107 |
108 | private renderContext = (context: IRealmContext) => {
109 | const { direction = 'both', mode = 'reportIndefinitely' } = this.props;
110 | if (context && this.realm !== context.realm) {
111 | this.forgetRealm();
112 | // Remember this sync session to avoid adding the notification more than once
113 | this.realm = context.realm;
114 | // Add a progress notification listeners, using process.nextTick to avoid calls to setState in the renderer
115 | process.nextTick(() => {
116 | const { syncSession } = this.realm;
117 | if (syncSession) {
118 | if (direction === 'download' || direction === 'both') {
119 | syncSession.addProgressNotification(
120 | 'download',
121 | mode,
122 | this.onDownloadProgress,
123 | );
124 | }
125 | if (direction === 'upload' || direction === 'both') {
126 | syncSession.addProgressNotification(
127 | 'upload',
128 | mode,
129 | this.onUploadProgress,
130 | );
131 | }
132 | }
133 | });
134 | }
135 | // Render the children
136 | const isDownloading = !!(
137 | this.state.download &&
138 | this.state.download.transferred < this.state.download.transferable
139 | );
140 | const isUploading = !!(
141 | this.state.upload &&
142 | this.state.upload.transferred < this.state.upload.transferable
143 | );
144 | const isLoading = isDownloading || isUploading;
145 | return this.props.children({
146 | ...this.state,
147 | ...context,
148 | isLoading,
149 | isDownloading,
150 | isUploading,
151 | });
152 | };
153 |
154 | private onDownloadProgress: Realm.Sync.ProgressNotificationCallback = (
155 | transferred: number,
156 | transferable: number,
157 | ) => {
158 | if (this.realm && this.realm.syncSession) {
159 | this.setState({ download: { transferred, transferable } });
160 | }
161 | };
162 |
163 | private onUploadProgress: Realm.Sync.ProgressNotificationCallback = (
164 | transferred: number,
165 | transferable: number,
166 | ) => {
167 | if (this.realm && this.realm.syncSession) {
168 | this.setState({ upload: { transferred, transferable } });
169 | }
170 | };
171 |
172 | private forgetRealm() {
173 | if (this.realm && this.realm.syncSession) {
174 | this.realm.syncSession.removeProgressNotification(
175 | this.onDownloadProgress,
176 | );
177 | this.realm.syncSession.removeProgressNotification(
178 | this.onUploadProgress,
179 | );
180 | delete this.realm;
181 | }
182 | }
183 | }
184 | return RealmProgress;
185 | };
186 |
--------------------------------------------------------------------------------
/src/RealmProvider.test.tsx:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Copyright 2018 Realm Inc.
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 | //
17 | ////////////////////////////////////////////////////////////////////////////
18 |
19 | import * as assert from 'assert';
20 | import * as React from 'react';
21 | import * as renderer from 'react-test-renderer';
22 |
23 | import { RealmProvider } from '.';
24 |
25 | describe('RealmProvider', () => {
26 | let tree: renderer.ReactTestRenderer;
27 |
28 | afterEach(() => {
29 | if (tree) {
30 | tree.unmount();
31 | tree = undefined;
32 | }
33 | // Delete the default file after the tests
34 | Realm.deleteFile({});
35 | });
36 |
37 | it('does not re-renders when Realm changes by default', async () => {
38 | let renderCount = 0;
39 | let realm: Realm;
40 | // Render it ..
41 | tree = renderer.create(
42 |
45 | {context => {
46 | realm = context.realm;
47 | renderCount++;
48 | return realm.objects('Person').length;
49 | }}
50 | ,
51 | );
52 |
53 | process.nextTick(() => {
54 | realm.write(() => {
55 | realm.create('Person', { name: 'Alice' });
56 | });
57 | // Update it again ...
58 | process.nextTick(() => {
59 | realm.write(() => {
60 | realm.create('Person', { name: 'Bob' });
61 | });
62 | });
63 | });
64 |
65 | // Wait for component to re-render
66 | await new Promise(resolve => process.nextTick(resolve));
67 | assert.equal(tree.toJSON(), '0');
68 | tree.unmount();
69 | // Just once
70 | assert.equal(renderCount, 1);
71 | });
72 |
73 | it('re-renders when Realm changes when asked to', async () => {
74 | // Create a context
75 | let renderCount = 0;
76 | let realm: Realm;
77 | // Render it ..
78 | tree = renderer.create(
79 |
83 | {context => {
84 | if (realm) {
85 | assert.equal(
86 | realm,
87 | context.realm,
88 | 'Expected Realm instance to be re-used',
89 | );
90 | } else {
91 | realm = context.realm;
92 | }
93 | renderCount++;
94 | return realm
95 | .objects<{ name: string }>('Person')
96 | .map(p => p.name)
97 | .join(' & ');
98 | }}
99 | ,
100 | );
101 |
102 | process.nextTick(() => {
103 | assert.equal(tree.toJSON(), '');
104 | realm.write(() => {
105 | realm.create('Person', { name: 'Alice' });
106 | });
107 | // Update it again ...
108 | process.nextTick(() => {
109 | assert.equal(tree.toJSON(), 'Alice');
110 | realm.write(() => {
111 | realm.create('Person', { name: 'Bob' });
112 | });
113 | });
114 | });
115 |
116 | // Wait for component to re-render
117 | await new Promise(resolve => process.nextTick(resolve));
118 | assert.equal(tree.toJSON(), 'Alice & Bob');
119 | tree.unmount();
120 | // Initially, creating Alice and creating Bob
121 | assert.equal(renderCount, 3);
122 | });
123 | });
124 |
--------------------------------------------------------------------------------
/src/RealmProvider.tsx:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Copyright 2018 Realm Inc.
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 | //
17 | ////////////////////////////////////////////////////////////////////////////
18 |
19 | import * as equal from 'fast-deep-equal';
20 | import * as memoizeOne from 'memoize-one';
21 | import * as React from 'react';
22 | import * as Realm from 'realm';
23 |
24 | import { IRealmContext } from '.';
25 |
26 | type RealmRenderer = (context: IRealmContext) => React.ReactChild;
27 |
28 | /**
29 | * Props passed to a RealmProvider component.
30 | *
31 | * Extends the [Realm.Configuration](https://realm.io/docs/javascript/latest/api/Realm.html#~Configuration) matching the
32 | * Realm JS version installed as peer dependency.
33 | */
34 | export interface IRealmProviderProps extends Realm.Configuration {
35 | children: React.ReactNode | RealmRenderer;
36 | updateOnChange?: boolean;
37 | }
38 |
39 | /**
40 | * Generates a RealmProvider wrapping a context provider.
41 | *
42 | * Use {@link createRealmContext} or the default RealmProvider instead of calling this directly.
43 | */
44 | export const generateRealmProvider = (
45 | WrappedProvider: React.Provider,
46 | ): React.ComponentType => {
47 | class RealmProvider extends React.Component {
48 | private changeListenersAdded: boolean = false;
49 | private realm: Realm;
50 | private memoizedRealm = memoizeOne((config: Realm.Configuration) => {
51 | // Another Realm was already memoized, let's forget about it
52 | if (this.realm) {
53 | this.forgetRealm();
54 | }
55 | // Create a new Realm
56 | const realm = new Realm(config);
57 | // Store it so we can clean up later
58 | this.realm = realm;
59 | // Return the Realm
60 | return realm;
61 | }, equal);
62 |
63 | // TODO: Add propTypes for non-TypeScript users
64 |
65 | public componentWillUnmount() {
66 | if (this.realm) {
67 | this.forgetRealm();
68 | }
69 | }
70 |
71 | /**
72 | * Renders the component.
73 | */
74 | public render() {
75 | const { children, updateOnChange, ...config } = this.props;
76 | const realm = this.memoizedRealm(config);
77 | // Register the change listeners if asked to and they were not already there
78 | if (updateOnChange && !this.changeListenersAdded) {
79 | this.addChangeListeners(realm);
80 | }
81 | // Collect the context
82 | const context: IRealmContext = { realm };
83 | return (
84 |
85 | {typeof children === 'function'
86 | ? (children as RealmRenderer)(context) // Assume a RealmRenderer
87 | : children}
88 |
89 | );
90 | }
91 |
92 | private forgetRealm() {
93 | if (!this.realm.isClosed) {
94 | // Ensure we don't register change listeners anymore
95 | this.removeChangeListeners(this.realm);
96 | this.realm.close();
97 | }
98 | this.changeListenersAdded = false;
99 | delete this.realm;
100 | }
101 |
102 | private addChangeListeners(realm: Realm) {
103 | realm.addListener('change', this.onRealmChange);
104 | realm.addListener('schema', this.onRealmChange);
105 | this.changeListenersAdded = true;
106 | }
107 |
108 | private removeChangeListeners(realm: Realm) {
109 | realm.removeListener('change', this.onRealmChange);
110 | realm.removeListener('schema', this.onRealmChange);
111 | this.changeListenersAdded = false;
112 | }
113 |
114 | private onRealmChange = () => {
115 | this.forceUpdate();
116 | };
117 | }
118 |
119 | return RealmProvider;
120 | };
121 |
--------------------------------------------------------------------------------
/src/RealmQuery.1-basic.test.tsx:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Copyright 2018 Realm Inc.
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 | //
17 | ////////////////////////////////////////////////////////////////////////////
18 |
19 | import * as assert from 'assert';
20 | import * as React from 'react';
21 | import * as renderer from 'react-test-renderer';
22 |
23 | import { IPerson, schema } from './test-utils/persons-realm';
24 |
25 | import { RealmConsumer, RealmProvider, RealmQuery } from '.';
26 |
27 | describe('RealmQuery (basic)', () => {
28 | let tree: renderer.ReactTestRenderer;
29 |
30 | afterEach(() => {
31 | if (tree) {
32 | tree.unmount();
33 | tree = null;
34 | }
35 | // Delete the default file after the tests
36 | Realm.deleteFile({});
37 | });
38 |
39 | it('will pass results as prop', done => {
40 | tree = renderer.create(
41 |
42 |
43 | {({ results }) => {
44 | assert(results);
45 | assert.equal(results.length, 0);
46 | return 'hi from render prop!';
47 | }}
48 |
49 | ,
50 | );
51 | // Asserting the tree matches the string which was returned
52 | assert.equal(tree.toJSON(), 'hi from render prop!');
53 | tree.unmount();
54 | done();
55 | });
56 |
57 | it('will work with a Consumer', done => {
58 | let step = 0;
59 |
60 | const finish = (realm: Realm) => {
61 | // Unmounting should close the Realm
62 | tree.unmount();
63 | tree = null;
64 | // Wait a tick before checking if the Realm closed ...
65 | process.nextTick(() => {
66 | assert.equal(realm.isClosed, true, 'the Realm was not closed');
67 | done();
68 | });
69 | };
70 |
71 | tree = renderer.create(
72 |
73 |
74 | {({ realm }) => {
75 | // Write a Person to the realm - delayed
76 | process.nextTick(() => {
77 | // Transition step
78 | assert.equal(step, 1);
79 | step++;
80 | // Create a person
81 | realm.write(() => {
82 | realm.create('Person', {
83 | name: 'John Doe',
84 | age: 42,
85 | });
86 | });
87 | });
88 | // But return right away ...
89 | return null;
90 | }}
91 |
92 |
93 | {({ realm, results }) => {
94 | if (step === 0) {
95 | step++;
96 | assert.equal(results.length, 0);
97 | // First the function is called when no persons exists
98 | } else if (step === 2) {
99 | step++;
100 | assert.equal(results.length, 1);
101 | finish(realm);
102 | } else {
103 | done(
104 | new Error(`RealmQuery rendered unexpectedly (step = ${step})`),
105 | );
106 | }
107 | return null;
108 | }}
109 |
110 | ,
111 | );
112 | // Asserting the tree matches the string which was returned
113 | assert.equal(tree.toJSON(), null);
114 | });
115 | });
116 |
--------------------------------------------------------------------------------
/src/RealmQuery.2-filter.test.tsx:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Copyright 2018 Realm Inc.
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 | //
17 | ////////////////////////////////////////////////////////////////////////////
18 |
19 | import * as assert from 'assert';
20 | import * as React from 'react';
21 | import * as renderer from 'react-test-renderer';
22 |
23 | import { IPerson, schema } from './test-utils/persons-realm';
24 |
25 | import { RealmProvider, RealmQuery } from '.';
26 |
27 | describe('RealmQuery (filter)', () => {
28 | let tree: renderer.ReactTestRenderer;
29 |
30 | afterEach(() => {
31 | if (tree) {
32 | tree.unmount();
33 | tree = null;
34 | }
35 | // Delete the default file after the tests
36 | Realm.deleteFile({});
37 | });
38 |
39 | it('will update, create objects and filter out when changed', done => {
40 | let step = 0;
41 | let alice: IPerson;
42 |
43 | tree = renderer.create(
44 |
45 |
46 | {({ realm, results }) => {
47 | if (step === 0) {
48 | step++;
49 | // First the function is called when no persons exists
50 | assert.equal(results.length, 0);
51 | // Create a person
52 | realm.write(() => {
53 | realm.create('Person', {
54 | name: 'John Doe',
55 | age: 42,
56 | });
57 | });
58 | } else if (step === 1) {
59 | step++;
60 | assert.equal(results.length, 1);
61 | // Create another person
62 | realm.write(() => {
63 | alice = realm.create('Person', {
64 | name: 'Alice',
65 | age: 40,
66 | });
67 | });
68 | } else if (step === 2) {
69 | step++;
70 | assert.equal(results.length, 2);
71 | // Create another person
72 | realm.write(() => {
73 | // Alice was younger
74 | alice.age = 20;
75 | });
76 | } else if (step === 3) {
77 | step++;
78 | // We expect that Alice is no longer in the results
79 | assert.equal(results.length, 1);
80 | // But John should still be there
81 | const person = results[0];
82 | assert.equal(person.name, 'John Doe');
83 | assert.equal(person.age, 42);
84 | // We're done!
85 | done();
86 | } else {
87 | done(
88 | new Error(`RealmQuery rendered unexpectedly (step = ${step})`),
89 | );
90 | }
91 | return null;
92 | }}
93 |
94 | ,
95 | );
96 | // Asserting the tree matches the string which was returned
97 | assert.equal(tree.toJSON(), null);
98 | });
99 | });
100 |
--------------------------------------------------------------------------------
/src/RealmQuery.3-sort.test.tsx:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Copyright 2018 Realm Inc.
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 | //
17 | ////////////////////////////////////////////////////////////////////////////
18 |
19 | import * as assert from 'assert';
20 | import * as React from 'react';
21 | import * as renderer from 'react-test-renderer';
22 |
23 | import { IPerson, schema } from './test-utils/persons-realm';
24 |
25 | import { RealmProvider, RealmQuery } from '.';
26 |
27 | describe('RealmQuery (sort)', () => {
28 | let tree: renderer.ReactTestRenderer;
29 |
30 | afterEach(() => {
31 | if (tree) {
32 | tree.unmount();
33 | tree = null;
34 | }
35 | // Delete the default file after the tests
36 | Realm.deleteFile({});
37 | });
38 |
39 | it('will update, create objects and sort when changed', done => {
40 | let step = 0;
41 | let alice: IPerson;
42 |
43 | tree = renderer.create(
44 |
45 |
46 | {({ realm, results }) => {
47 | if (step === 0) {
48 | step++;
49 | // First the function is called when no persons exists
50 | assert.equal(results.length, 0);
51 | // Create a person
52 | realm.write(() => {
53 | realm.create('Person', {
54 | name: 'John Doe',
55 | age: 42,
56 | });
57 | });
58 | } else if (step === 1) {
59 | step++;
60 | assert.equal(results.length, 1);
61 | // Create another person
62 | realm.write(() => {
63 | alice = realm.create('Person', {
64 | name: 'Alice',
65 | age: 40,
66 | });
67 | });
68 | } else if (step === 2) {
69 | step++;
70 | assert.equal(results.length, 2);
71 | // We expect Alice first and then John
72 | assert.equal(results[0].name, 'Alice');
73 | assert.equal(results[1].name, 'John Doe');
74 | // Create another person
75 | realm.write(() => {
76 | // Alice was older
77 | alice.age = 60;
78 | });
79 | } else if (step === 3) {
80 | step++;
81 | assert.equal(results.length, 2);
82 | // We expect John first and then Alice
83 | assert.equal(results[0].name, 'John Doe');
84 | assert.equal(results[1].name, 'Alice');
85 | // We're done!
86 | done();
87 | } else {
88 | done(
89 | new Error(`RealmQuery rendered unexpectedly (step = ${step})`),
90 | );
91 | }
92 | return null;
93 | }}
94 |
95 | ,
96 | );
97 | // Asserting the tree matches the string which was returned
98 | assert.equal(tree.toJSON(), null);
99 | });
100 | });
101 |
--------------------------------------------------------------------------------
/src/RealmQuery.4-type-prop.test.tsx:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Copyright 2018 Realm Inc.
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 | //
17 | ////////////////////////////////////////////////////////////////////////////
18 |
19 | import * as assert from 'assert';
20 | import * as React from 'react';
21 | import * as renderer from 'react-test-renderer';
22 |
23 | import { IDog, IPerson, schema } from './test-utils/persons-realm';
24 |
25 | import { RealmProvider, RealmQuery } from '.';
26 |
27 | // This test doesn't document public methods and properties
28 | // tslint:disable:completed-docs
29 |
30 | describe('RealmQuery (type prop)', () => {
31 | let tree: renderer.ReactTestRenderer;
32 |
33 | afterEach(() => {
34 | if (tree) {
35 | tree.unmount();
36 | tree = null;
37 | }
38 | // Delete the default file after the tests
39 | Realm.deleteFile({});
40 | });
41 |
42 | it('will update when prop change', done => {
43 | let step = 0;
44 |
45 | interface IListState {
46 | type: 'Person' | 'Dog';
47 | }
48 |
49 | class List extends React.Component<{}, IListState> {
50 | public state: IListState = { type: 'Person' };
51 |
52 | public render() {
53 | return (
54 |
55 |
56 | {({ realm, results }) => {
57 | if (step === 0) {
58 | step++;
59 | // First the function is called when no persons exists
60 | assert.equal(results.length, 0);
61 | // Create a person
62 | realm.write(() => {
63 | // The person Alice
64 | const alice = realm.create('Person', {
65 | name: 'Alice',
66 | });
67 | // The person Bob
68 | realm.create('Person', { name: 'Bob' });
69 | // The dog Charlie
70 | const charlie = realm.create('Dog', {
71 | name: 'Charlie',
72 | });
73 | // Which belongs to Alice
74 | alice.dogs.push(charlie);
75 | });
76 | } else if (step === 1) {
77 | step++;
78 | assert.equal(results.length, 2);
79 | // We expect Alice first and then John
80 | assert.equal(results[0].name, 'Alice');
81 | assert.equal(results[1].name, 'Bob');
82 | // Change the query to return Dogs
83 | this.setState({ type: 'Dog' });
84 | } else if (step === 2) {
85 | step++;
86 | assert.equal(results.length, 1);
87 | // We expect Alice's dog charlie
88 | assert.equal(results[0].name, 'Charlie');
89 | // We're done!
90 | done();
91 | } else {
92 | done(
93 | new Error(
94 | `RealmQuery rendered unexpectedly (step = ${step})`,
95 | ),
96 | );
97 | }
98 | return null;
99 | }}
100 |
101 |
102 | );
103 | }
104 | }
105 |
106 | tree = renderer.create(
);
107 | // Asserting the tree matches the string which was returned
108 | assert.equal(tree.toJSON(), null);
109 | });
110 | });
111 |
--------------------------------------------------------------------------------
/src/RealmQuery.5-filter-prop.test.tsx:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Copyright 2018 Realm Inc.
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 | //
17 | ////////////////////////////////////////////////////////////////////////////
18 |
19 | import * as assert from 'assert';
20 | import * as React from 'react';
21 | import * as renderer from 'react-test-renderer';
22 |
23 | import { IPerson, schema } from './test-utils/persons-realm';
24 |
25 | import { RealmProvider, RealmQuery } from '.';
26 |
27 | // This test doesn't document public methods and properties
28 | // tslint:disable:completed-docs
29 |
30 | describe('RealmQuery (filter prop)', () => {
31 | let tree: renderer.ReactTestRenderer;
32 |
33 | afterEach(() => {
34 | if (tree) {
35 | tree.unmount();
36 | tree = null;
37 | }
38 | // Delete the default file after the tests
39 | Realm.deleteFile({});
40 | });
41 |
42 | it('will update when prop change', done => {
43 | let step = 0;
44 |
45 | interface IPersonListState {
46 | threashold: number;
47 | }
48 |
49 | class PersonList extends React.Component<{}, IPersonListState> {
50 | public state: IPersonListState = { threashold: 30 };
51 |
52 | public render() {
53 | return (
54 |
55 | $0', this.state.threashold]}
58 | >
59 | {({ realm, results }) => {
60 | if (step === 0) {
61 | step++;
62 | // First the function is called when no persons exists
63 | assert.equal(results.length, 0);
64 | // Create a person
65 | realm.write(() => {
66 | // John Doe
67 | realm.create('Person', {
68 | name: 'John Doe',
69 | age: 42,
70 | });
71 | // Alice
72 | realm.create('Person', {
73 | name: 'Alice',
74 | age: 40,
75 | });
76 | });
77 | } else if (step === 1) {
78 | step++;
79 | assert.equal(results.length, 2);
80 | // Change the filter to cut out Alice
81 | process.nextTick(() => {
82 | this.setState({ threashold: 41 });
83 | });
84 | } else if (step === 2) {
85 | step++;
86 | // We expect that Alice is no longer in the results
87 | assert.equal(results.length, 1);
88 | // But John should still be there
89 | const person = results[0];
90 | assert.equal(person.name, 'John Doe');
91 | assert.equal(person.age, 42);
92 | // We're done!
93 | done();
94 | } else {
95 | done(
96 | new Error(
97 | `RealmQuery rendered unexpectedly (step = ${step})`,
98 | ),
99 | );
100 | }
101 | return null;
102 | }}
103 |
104 |
105 | );
106 | }
107 | }
108 |
109 | tree = renderer.create();
110 | // Asserting the tree matches the string which was returned
111 | assert.equal(tree.toJSON(), null);
112 | });
113 | });
114 |
--------------------------------------------------------------------------------
/src/RealmQuery.6-sort-prop.test.tsx:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Copyright 2018 Realm Inc.
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 | //
17 | ////////////////////////////////////////////////////////////////////////////
18 |
19 | import * as assert from 'assert';
20 | import * as React from 'react';
21 | import * as renderer from 'react-test-renderer';
22 |
23 | import { IPerson, schema } from './test-utils/persons-realm';
24 |
25 | import { RealmProvider, RealmQuery, RealmSorting } from '.';
26 |
27 | // This test doesn't document public methods and properties
28 | // tslint:disable:completed-docs
29 |
30 | describe('RealmQuery (sort prop)', () => {
31 | let tree: renderer.ReactTestRenderer;
32 |
33 | afterEach(() => {
34 | if (tree) {
35 | tree.unmount();
36 | tree = null;
37 | }
38 | // Delete the default file after the tests
39 | Realm.deleteFile({});
40 | });
41 |
42 | it('will update when prop change', done => {
43 | let step = 0;
44 |
45 | interface IPersonListState {
46 | sort: RealmSorting;
47 | }
48 |
49 | class PersonList extends React.Component<{}, IPersonListState> {
50 | public state: IPersonListState = { sort: 'name' };
51 |
52 | public render() {
53 | return (
54 |
55 |
56 | {({ realm, results }) => {
57 | if (step === 0) {
58 | step++;
59 | // First the function is called when no persons exists
60 | assert.equal(results.length, 0);
61 | // Create a person
62 | realm.write(() => {
63 | // John Doe
64 | realm.create('Person', {
65 | name: 'John Doe',
66 | age: 42,
67 | });
68 | // Alice
69 | realm.create('Person', {
70 | name: 'Alice',
71 | age: 50,
72 | });
73 | });
74 | } else if (step === 1) {
75 | step++;
76 | assert.equal(results.length, 2);
77 | // We expect Alice first and then John
78 | assert.equal(results[0].name, 'Alice');
79 | assert.equal(results[1].name, 'John Doe');
80 | // Create another person
81 | this.setState({ sort: 'age' });
82 | } else if (step === 2) {
83 | step++;
84 | assert.equal(results.length, 2);
85 | // We expect John first and then Alice
86 | assert.equal(results[0].name, 'John Doe');
87 | assert.equal(results[1].name, 'Alice');
88 | // Reverse the sorting
89 | this.setState({ sort: ['age', true] });
90 | } else if (step === 3) {
91 | step++;
92 | assert.equal(results.length, 2);
93 | // We expect Alice first and then John
94 | assert.equal(results[0].name, 'Alice');
95 | assert.equal(results[1].name, 'John Doe');
96 | // We're done!
97 | done();
98 | } else {
99 | done(
100 | new Error(
101 | `RealmQuery rendered unexpectedly (step = ${step})`,
102 | ),
103 | );
104 | }
105 | return null;
106 | }}
107 |
108 |
109 | );
110 | }
111 | }
112 |
113 | tree = renderer.create();
114 | // Asserting the tree matches the string which was returned
115 | assert.equal(tree.toJSON(), null);
116 | });
117 | });
118 |
--------------------------------------------------------------------------------
/src/RealmQuery.7-swapped-realm.test.tsx:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Copyright 2018 Realm Inc.
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 | //
17 | ////////////////////////////////////////////////////////////////////////////
18 |
19 | import * as assert from 'assert';
20 | import * as React from 'react';
21 | import * as renderer from 'react-test-renderer';
22 |
23 | import { IPerson, schema } from './test-utils/persons-realm';
24 |
25 | import { IRealmQueryProps, RealmProvider, RealmQuery } from '.';
26 |
27 | // This test doesn't document public methods and properties
28 | // tslint:disable:completed-docs
29 |
30 | describe('RealmQuery (swapped Realm)', () => {
31 | let tree: renderer.ReactTestRenderer;
32 |
33 | afterEach(() => {
34 | if (tree) {
35 | tree.unmount();
36 | tree = null;
37 | }
38 | // Delete the default file after the tests
39 | Realm.deleteFile({});
40 | });
41 |
42 | it('will update when config props change', done => {
43 | let step = 0;
44 | let previousRealm: Realm;
45 |
46 | function finish(realm: Realm) {
47 | // Unmounting should close the Realm
48 | tree.unmount();
49 | tree = null;
50 | // Wait a tick before checking if the Realm closed ...
51 | process.nextTick(() => {
52 | assert.equal(realm.isClosed, true, 'the Realm was not closed');
53 | done();
54 | });
55 | }
56 |
57 | // Define an typed version of the RealmQuery component
58 | interface IPersonQueryProps extends IRealmQueryProps {
59 | type: 'Person';
60 | }
61 | const TypedRealmQuery = RealmQuery as React.ComponentType<
62 | IPersonQueryProps
63 | >;
64 |
65 | interface IWrappingComponentState {
66 | config: Realm.PartialConfiguration;
67 | }
68 |
69 | class WrappingComponent extends React.Component<
70 | {},
71 | IWrappingComponentState
72 | > {
73 | public state: IWrappingComponentState = { config: {} };
74 |
75 | public render() {
76 | return (
77 |
78 |
79 | {({ realm, results }) => {
80 | if (step === 0) {
81 | step++;
82 | assert.equal(results.length, 0);
83 | previousRealm = realm;
84 | // Create a person
85 | realm.write(() => {
86 | realm.create('Person', {
87 | name: 'John Doe',
88 | age: 42,
89 | });
90 | });
91 | // First the function is called when no persons exists
92 | } else if (step === 1) {
93 | step++;
94 | assert.equal(
95 | previousRealm,
96 | realm,
97 | 'Expected the Realm instance to be reused',
98 | );
99 | assert.equal(
100 | realm.readOnly,
101 | false,
102 | 'Expected Realm to be read-write',
103 | );
104 | assert.equal(results.length, 1);
105 | assert.equal(results[0].name, 'John Doe');
106 | // Reopen the Realm as read-only
107 | this.setState({ config: { readOnly: true } });
108 | } else if (step === 2) {
109 | step++;
110 | assert.notEqual(
111 | previousRealm,
112 | realm,
113 | 'Expected the Realm to have changed',
114 | );
115 | assert.equal(
116 | realm.readOnly,
117 | true,
118 | 'Expected Realm to be read-only',
119 | );
120 | // We're expecting the results object to have changed, but contain elements with the same values
121 | assert.equal(results[0].name, 'John Doe');
122 | finish(realm);
123 | } else {
124 | done(
125 | new Error(
126 | `RealmQuery rendered unexpectedly (step = ${step})`,
127 | ),
128 | );
129 | }
130 | return null;
131 | }}
132 |
133 |
134 | );
135 | }
136 | }
137 |
138 | tree = renderer.create();
139 | // Asserting the tree matches the string which was returned
140 | assert.equal(tree.toJSON(), null);
141 | });
142 | });
143 |
--------------------------------------------------------------------------------
/src/RealmQuery.tsx:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Copyright 2018 Realm Inc.
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 | //
17 | ////////////////////////////////////////////////////////////////////////////
18 |
19 | import * as memoizeOne from 'memoize-one';
20 | import * as React from 'react';
21 | import * as Realm from 'realm';
22 |
23 | import { IRealmContext } from '.';
24 |
25 | interface IValue {
26 | results: Realm.Results;
27 | realm: Realm;
28 | }
29 |
30 | type QueryChild = (value: IValue) => React.ReactChild;
31 |
32 | /**
33 | * Something to filter the results by.
34 | */
35 | export type Filtering = string | any[];
36 |
37 | /**
38 | * Something to sort the results by.
39 | */
40 | export type Sorting = string | Realm.SortDescriptor | Realm.SortDescriptor[];
41 |
42 | /**
43 | * Props passed to a RealmQuery component.
44 | */
45 | export interface IRealmQueryProps {
46 | children: QueryChild;
47 | type: string;
48 | filter?: Filtering;
49 | sort?: Sorting;
50 | }
51 |
52 | /**
53 | * Generates a RealmQuery wrapping a context consumer.
54 | *
55 | * Use {@link createRealmContext} or the default RealmQuery instead of calling this directly.
56 | */
57 | export const generateRealmQuery = (
58 | WrappedConsumer: React.Consumer,
59 | ): React.ComponentType> => {
60 | class RealmQuery extends React.Component> {
61 | private results?: Realm.Results;
62 | private memoizedResults = memoizeOne(
63 | (realm: Realm, type: string, filter: Filtering, sort: Sorting) => {
64 | // Forget any results we have already returned
65 | this.forgetResults();
66 |
67 | // Start with the type
68 | let results = realm.objects(type);
69 | // Filtering
70 | if (filter) {
71 | if (typeof filter === 'string') {
72 | results = results.filtered(filter);
73 | } else {
74 | const [query, ...args] = filter;
75 | results = results.filtered(query as string, ...args);
76 | }
77 | }
78 | // Sorting
79 | if (sort) {
80 | if (typeof sort === 'string') {
81 | results = results.sorted(sort);
82 | } else if (Array.isArray(sort)) {
83 | results =
84 | sort.length === 2 &&
85 | typeof sort[0] === 'string' &&
86 | typeof sort[1] === 'boolean'
87 | ? results.sorted(sort[0] as string, sort[1] as boolean)
88 | : results.sorted(sort as Realm.SortDescriptor[]);
89 | } else {
90 | // TODO: Implement sorting on multiple fields
91 | throw new Error(
92 | 'Sorting reverse or on multiple properties are not implemented yet',
93 | );
94 | }
95 | }
96 |
97 | // TODO: Handle an invalid result
98 | // Register a listener - if we do this on a read-only Realm, Realm JS will throw:
99 | // "Cannot create asynchronous query for immutable Realms"
100 | if (!realm.readOnly) {
101 | results.addListener(this.resultsChanged);
102 | }
103 | // Save this for later use
104 | this.results = results;
105 |
106 | // Return
107 | return results;
108 | },
109 | );
110 |
111 | // TODO: Add propTypes for non-TypeScript users
112 | // TODO: Allow the query to take a custom consumer as a prop
113 |
114 | public componentWillUnmount() {
115 | this.forgetResults();
116 | }
117 |
118 | /**
119 | * Renders the component.
120 | */
121 | public render() {
122 | return {this.renderContext};
123 | }
124 |
125 | private renderContext = (value: IRealmContext) => {
126 | // The results are not available yet (or we just forgot them)
127 | const { type, filter, sort } = this.props;
128 | const results = this.memoizedResults(value.realm, type, filter, sort);
129 | // Calling the function passed as children with the derived context
130 | return this.props.children({ results, realm: value.realm });
131 | };
132 |
133 | private forgetResults() {
134 | if (this.results) {
135 | this.results.removeAllListeners();
136 | delete this.results;
137 | }
138 | }
139 |
140 | private resultsChanged: Realm.CollectionChangeCallback = (
141 | collection,
142 | change,
143 | ) => {
144 | // This might fire although nothing changed
145 | const { deletions, insertions, modifications } = change;
146 | const changes =
147 | deletions.length + insertions.length + modifications.length;
148 | if (changes > 0) {
149 | this.forceUpdate();
150 | }
151 | };
152 | }
153 |
154 | return RealmQuery;
155 | };
156 |
--------------------------------------------------------------------------------
/src/index.test.tsx:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Copyright 2018 Realm Inc.
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 | //
17 | ////////////////////////////////////////////////////////////////////////////
18 |
19 | import * as assert from 'assert';
20 | import * as React from 'react';
21 | import * as renderer from 'react-test-renderer';
22 | import * as Realm from 'realm';
23 |
24 | import {
25 | createRealmContext,
26 | RealmConsumer as DefaultRealmConsumer,
27 | RealmProvider as DefaultRealmProvider,
28 | } from '.';
29 |
30 | describe('realm-realm-context', () => {
31 | let tree: renderer.ReactTestRenderer;
32 |
33 | afterEach(() => {
34 | if (tree) {
35 | tree.unmount();
36 | tree = undefined;
37 | }
38 | // Delete the default file after the tests
39 | Realm.deleteFile({});
40 | });
41 |
42 | describe('createRealmContext', () => {
43 | it('returns a RealmProvider and a RealmConsumer', () => {
44 | // Create a context
45 | const result = createRealmContext();
46 | // Assert something about it
47 | assert.equal(Object.keys(result).length, 7);
48 | const {
49 | RealmProvider,
50 | RealmConsumer,
51 | RealmInitializer,
52 | RealmQuery,
53 | RealmConnection,
54 | RealmProgress,
55 | withRealm,
56 | } = result;
57 | assert(RealmProvider);
58 | assert(RealmConsumer);
59 | assert(RealmInitializer);
60 | assert(RealmQuery);
61 | assert(RealmConnection);
62 | assert(RealmProgress);
63 | assert(withRealm);
64 | });
65 |
66 | it('calls any function passed as the children prop', () => {
67 | const { RealmProvider } = createRealmContext();
68 | tree = renderer.create(
69 |
70 | {props => {
71 | // Assert exactly 1 properties passed through props
72 | assert.equal(Object.keys(props).length, 1);
73 | // Assert that a Realm is passed as property
74 | assert(props.realm instanceof Realm);
75 | // Signal that the children prop callback was called
76 | return 'hi from render prop!';
77 | }}
78 | ,
79 | );
80 | // Asserting the tree matches the string which was returned
81 | assert.equal(tree.toJSON(), 'hi from render prop!');
82 | tree.unmount();
83 | });
84 |
85 | it('renders the RealmConsumer when wrapped in RealmProvider', () => {
86 | // Create a context
87 | const { RealmProvider, RealmConsumer } = createRealmContext();
88 | // Render it ..
89 | tree = renderer.create(
90 |
91 |
92 | {props => {
93 | // Assert exactly 1 properties passed through props
94 | assert.equal(Object.keys(props).length, 1);
95 | // Assert that a Realm is passed as property
96 | assert(props.realm instanceof Realm);
97 | // Signal that the children prop callback was called
98 | return 'hi from context consumer!';
99 | }}
100 |
101 | ,
102 | );
103 | assert.equal(tree.toJSON(), 'hi from context consumer!');
104 | tree.unmount();
105 | });
106 | });
107 |
108 | describe('default RealmProvider', () => {
109 | it('calls any function passed as the children prop', () => {
110 | tree = renderer.create(
111 |
112 | {() => {
113 | return 'hi from providers render prop!';
114 | }}
115 | ,
116 | );
117 | // Asserting the tree matches the string which was returned
118 | assert.equal(tree.toJSON(), 'hi from providers render prop!');
119 | tree.unmount();
120 | });
121 | });
122 |
123 | describe('default RealmProvider & RealmConsumer', () => {
124 | it('calls any function passed as the children prop', () => {
125 | tree = renderer.create(
126 |
127 |
128 | {() => {
129 | return 'hi from consumers render prop!';
130 | }}
131 |
132 | ,
133 | );
134 | // Asserting the tree matches the string which was returned
135 | assert.equal(tree.toJSON(), 'hi from consumers render prop!');
136 | tree.unmount();
137 | });
138 | });
139 | });
140 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Copyright 2018 Realm Inc.
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 | //
17 | ////////////////////////////////////////////////////////////////////////////
18 |
19 | import * as React from 'react';
20 | import * as Realm from 'realm';
21 |
22 | import { generateRealmConnection } from './RealmConnection';
23 | import { generateRealmConsumer, IRealmConsumerProps } from './RealmConsumer';
24 | import {
25 | generateRealmInitializer,
26 | IRealmInitializerProps,
27 | } from './RealmInitializer';
28 | import {
29 | generateRealmProgress,
30 | IRealmProgressProps,
31 | IRealmProgressValue,
32 | } from './RealmProgress';
33 | import { generateRealmProvider, IRealmProviderProps } from './RealmProvider';
34 | import { generateRealmQuery, IRealmQueryProps, Sorting } from './RealmQuery';
35 | import { generateWithRealm } from './withRealm';
36 |
37 | export {
38 | IRealmConsumerProps,
39 | IRealmInitializerProps,
40 | IRealmProviderProps,
41 | IRealmProgressProps,
42 | IRealmProgressValue,
43 | IRealmQueryProps,
44 | };
45 |
46 | /**
47 | * The context passed from a RealmProvider to any of its RealmConsumer
48 | */
49 | export interface IRealmContext {
50 | realm: Realm;
51 | }
52 |
53 | const createRealmContext = () => {
54 | const context = React.createContext(null);
55 | const Consumer = generateRealmConsumer(context.Consumer);
56 | return {
57 | RealmProvider: generateRealmProvider(context.Provider),
58 | RealmConsumer: Consumer,
59 | RealmQuery: generateRealmQuery(Consumer),
60 | RealmInitializer: generateRealmInitializer(Consumer),
61 | RealmConnection: generateRealmConnection(Consumer),
62 | RealmProgress: generateRealmProgress(Consumer),
63 | withRealm: generateWithRealm(Consumer),
64 | };
65 | };
66 |
67 | // Export a function that creates Realm contexts
68 | export { createRealmContext, Sorting as RealmSorting };
69 |
70 | // Create and export default RealmProvider and RealmConsumer
71 | const {
72 | /**
73 | * The default RealmProvider.
74 | *
75 | * If you're opening multiple Realms create separate contexts using {@link createRealmContext}.
76 | */
77 | RealmProvider,
78 |
79 | /**
80 | * The default RealmConsumer, which will get its Realm from the default RealmProvider.
81 | *
82 | * If you're opening multiple Realms create separate contexts using {@link createRealmContext}.
83 | */
84 | RealmConsumer,
85 |
86 | /**
87 | * The default RealmQuery, which will get its Realm from the default RealmProvider.
88 | *
89 | * If you're opening multiple Realms create separate contexts using {@link createRealmContext}.
90 | */
91 | RealmQuery,
92 |
93 | /**
94 | * The default RealmInitializer, which will get its Realm from the default RealmProvider.
95 | *
96 | * If you're opening multiple Realms create separate contexts using {@link createRealmContext}.
97 | */
98 | RealmInitializer,
99 |
100 | /**
101 | * The default RealmConnection, which will get its Realm from the default RealmProvider.
102 | *
103 | * If you're opening multiple Realms create separate contexts using {@link createRealmContext}.
104 | */
105 | RealmConnection,
106 |
107 | /**
108 | * The default RealmProgress, which will get its Realm from the default RealmProvider.
109 | *
110 | * If you're opening multiple Realms create separate contexts using {@link createRealmContext}.
111 | */
112 | RealmProgress,
113 |
114 | /**
115 | * The default withRealm HOC, which will get its Realm from the default RealmProvider.
116 | *
117 | * If you're opening multiple Realms create separate contexts using {@link createRealmContext}.
118 | */
119 | withRealm,
120 | } = createRealmContext();
121 |
122 | export {
123 | RealmProvider,
124 | RealmConsumer,
125 | RealmQuery,
126 | RealmInitializer,
127 | RealmConnection,
128 | RealmProgress,
129 | withRealm,
130 | };
131 |
--------------------------------------------------------------------------------
/src/test-utils/persons-realm.ts:
--------------------------------------------------------------------------------
1 | import * as Realm from 'realm';
2 |
3 | /**
4 | * A person which has a name, an age and a list of dogs.
5 | * It's used by the example Realms in tests.
6 | */
7 | export interface IPerson {
8 | name: string;
9 | age?: number;
10 | dogs: Realm.List;
11 | }
12 |
13 | /**
14 | * A dog which has a name, an age.
15 | * It's used by the example Realms in tests.
16 | */
17 | export interface IDog {
18 | name: string;
19 | age?: number;
20 | }
21 |
22 | /**
23 | * An example Realm schema which has just a Person object schema.
24 | * It's used by the example Realms in tests.
25 | */
26 | export const schema: Realm.ObjectSchema[] = [
27 | {
28 | name: 'Person',
29 | properties: {
30 | name: 'string',
31 | age: 'int?',
32 | dogs: 'Dog[]',
33 | },
34 | },
35 | {
36 | name: 'Dog',
37 | properties: {
38 | name: 'string',
39 | age: 'int?',
40 | },
41 | },
42 | ];
43 |
--------------------------------------------------------------------------------
/src/test-utils/realm-js-logging.ts:
--------------------------------------------------------------------------------
1 | import Debug from 'debug';
2 | import * as Realm from 'realm';
3 |
4 | enum SyncLogLevel {
5 | all,
6 | trace,
7 | debug,
8 | detail,
9 | info,
10 | warn,
11 | error,
12 | fatal,
13 | off,
14 | }
15 |
16 | const realmDebug = Debug('realm');
17 | if (typeof (Realm.Sync as any).setSyncLogger === 'function') {
18 | (Realm.Sync as any).setSyncLogger((level: number, message: string) => {
19 | realmDebug(`[${SyncLogLevel[level]}] ${message}`);
20 | });
21 | } else {
22 | throw new Error('Expected Realm.Sync.setSyncLogger to be a function');
23 | }
24 |
25 | if (!('DEBUG' in process.env)) {
26 | // tslint:disable-next-line:no-console
27 | console.log('Run with DEBUG=realm to get Realm JS log output');
28 | }
29 |
--------------------------------------------------------------------------------
/src/test-utils/segfault-handler.ts:
--------------------------------------------------------------------------------
1 | import * as SegfaultHandler from 'segfault-handler';
2 | SegfaultHandler.registerHandler('crash.log');
3 | // tslint:disable-next-line:no-console
4 | console.log('Registered a segfault handler.');
5 |
--------------------------------------------------------------------------------
/src/test-utils/with-ros.ts:
--------------------------------------------------------------------------------
1 | import { ITestCallbackContext } from 'mocha';
2 | import * as Realm from 'realm';
3 | import { v4 as uuid } from 'uuid';
4 |
5 | const { REALM_OBJECT_SERVER_URL } = process.env;
6 |
7 | if (typeof REALM_OBJECT_SERVER_URL === 'string') {
8 | // tslint:disable-next-line:no-console
9 | console.log(`Running tests requiring ROS against ${REALM_OBJECT_SERVER_URL}`);
10 | } else {
11 | // tslint:disable-next-line:no-console
12 | console.log(
13 | 'Define "REALM_OBJECT_SERVER_URL" environment variable to run tests that require ROS',
14 | );
15 | }
16 |
17 | interface IRealmObjectServer {
18 | url: string;
19 | createTestUser: () => Promise;
20 | }
21 |
22 | interface ITestCallbackContextWithROS extends ITestCallbackContext {
23 | ros: IRealmObjectServer;
24 | }
25 |
26 | const ros: IRealmObjectServer = {
27 | url: REALM_OBJECT_SERVER_URL,
28 | createTestUser: () => {
29 | return Realm.Sync.User.login(
30 | REALM_OBJECT_SERVER_URL,
31 | Realm.Sync.Credentials.nickname(`react-realm-context-tests-${uuid()}`),
32 | );
33 | },
34 | };
35 |
36 | /**
37 | * Runs tests only if a Realm Object Server was started by the environment running the tests.
38 | */
39 | export const withROS = {
40 | it: (
41 | expectation: string,
42 | callback?: (
43 | this: ITestCallbackContextWithROS,
44 | done: MochaDone,
45 | ) => PromiseLike | void,
46 | ) => {
47 | if (typeof REALM_OBJECT_SERVER_URL === 'string') {
48 | it(expectation, function(...args) {
49 | // Communicating with ROS takes longer than other tests
50 | this.timeout(5000);
51 | return callback.call({ ...this, ros }, ...args);
52 | });
53 | } else {
54 | it.skip(expectation);
55 | }
56 | },
57 | };
58 |
--------------------------------------------------------------------------------
/src/withRealm.test.tsx:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Copyright 2018 Realm Inc.
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 | //
17 | ////////////////////////////////////////////////////////////////////////////
18 |
19 | import * as assert from 'assert';
20 | import * as React from 'react';
21 | import * as renderer from 'react-test-renderer';
22 |
23 | import { IPerson, schema } from './test-utils/persons-realm';
24 |
25 | import { RealmConsumer, RealmInitializer, RealmProvider, withRealm } from '.';
26 |
27 | // This test creates more than one class and doesn't document their public methods.
28 | // tslint:disable:max-classes-per-file
29 | // tslint:disable:completed-docs
30 |
31 | describe('withRealm injector', () => {
32 | let tree: renderer.ReactTestRenderer;
33 |
34 | afterEach(() => {
35 | // Delete the default file after the tests
36 | Realm.deleteFile({});
37 | });
38 |
39 | it('will inject the default `realm` prop', () => {
40 | let realmReference: Realm;
41 |
42 | interface ISomeComponentProps {
43 | realm: Realm;
44 | greeting: string;
45 | }
46 |
47 | // tslint:disable-next-line:complete-docs
48 | class SomeComponent extends React.Component {
49 | public render() {
50 | const names = this.props.realm
51 | .objects<{ name: 'string' }>('Person')
52 | .map(p => p.name)
53 | .join(', ');
54 | return `${this.props.greeting} ${names}`;
55 | }
56 | }
57 |
58 | const SomeEnhancedComponent = withRealm(SomeComponent);
59 |
60 | tree = renderer.create(
61 |
62 |
63 | {({ realm }) => {
64 | realmReference = realm;
65 | realm.create('Person', { name: 'Bobby Boy' });
66 | }}
67 |
68 |
69 | ,
70 | );
71 |
72 | // Asserting the tree matches the string which was returned
73 | assert.equal(tree.toJSON(), 'Hi there Bobby Boy');
74 | // Unmounting should close the Realm
75 | tree.unmount();
76 | tree = null;
77 | // Check that unmounting did indeed close the Realm
78 | assert.equal(realmReference.isClosed, true, 'the Realm was not closed');
79 | });
80 |
81 | it('will inject a named `myRealm` prop', () => {
82 | let realmReference: Realm;
83 |
84 | interface ISomeComponentProps {
85 | myRealm: Realm;
86 | greeting: string;
87 | }
88 |
89 | class SomeComponent extends React.Component {
90 | public render() {
91 | const names = this.props.myRealm
92 | .objects<{ name: 'string' }>('Person')
93 | .map(p => p.name)
94 | .join(', ');
95 | return `${this.props.greeting} ${names}`;
96 | }
97 | }
98 |
99 | const SomeEnhancedComponent = withRealm(SomeComponent, 'myRealm');
100 |
101 | tree = renderer.create(
102 |
103 |
104 | {({ realm }) => {
105 | realmReference = realm;
106 | realm.create('Person', { name: 'Bobby Boy' });
107 | }}
108 |
109 |
110 | ,
111 | );
112 |
113 | // Asserting the tree matches the string which was returned
114 | assert.equal(tree.toJSON(), 'Hi there Bobby Boy');
115 | // Unmounting should close the Realm
116 | tree.unmount();
117 | tree = null;
118 | // Check that unmounting did indeed close the Realm
119 | assert.equal(realmReference.isClosed, true, 'the Realm was not closed');
120 | });
121 |
122 | it('will pass on props given when calling withRealm', async () => {
123 | let realmReference: Realm;
124 |
125 | let defaultRenderCount = 0;
126 | let updatingRenderCount = 0;
127 |
128 | class SomeComponent extends React.Component<{
129 | children?: React.ReactNode;
130 | realm: Realm;
131 | }> {
132 | public render() {
133 | const { realm } = this.props;
134 | realmReference = realm;
135 | updatingRenderCount++;
136 | return realm
137 | .objects<{ name: string }>('Person')
138 | .map(p => p.name)
139 | .join(' & ');
140 | }
141 | }
142 |
143 | const SomeEnhancedComponent = withRealm(SomeComponent, 'realm', {
144 | updateOnChange: true,
145 | });
146 |
147 | tree = renderer.create(
148 |
151 |
152 | {({ realm }) => {
153 | process.nextTick(() => {
154 | realm.write(() => {
155 | realm.create('Person', { name: 'Alice' });
156 | });
157 | // Update it again ...
158 | process.nextTick(() => {
159 | realm.write(() => {
160 | realm.create('Person', { name: 'Bob' });
161 | });
162 | });
163 | });
164 | defaultRenderCount++;
165 | return null;
166 | }}
167 |
168 |
169 | ,
170 | );
171 |
172 | // Wait for component to re-render
173 | await new Promise(resolve => process.nextTick(resolve));
174 |
175 | assert.equal(tree.toJSON(), 'Alice & Bob');
176 | tree.unmount();
177 | tree = null;
178 |
179 | assert.equal(defaultRenderCount, 1);
180 | // Initially, creating Alice and creating Bob
181 | assert.equal(updatingRenderCount, 3);
182 | // Check that unmounting did indeed close the Realm
183 | assert.equal(realmReference.isClosed, true, 'the Realm was not closed');
184 | });
185 | });
186 |
--------------------------------------------------------------------------------
/src/withRealm.tsx:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Copyright 2018 Realm Inc.
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 | //
17 | ////////////////////////////////////////////////////////////////////////////
18 |
19 | import * as React from 'react';
20 | import * as Realm from 'realm';
21 |
22 | import { IRealmConsumerProps } from '.';
23 |
24 | /**
25 | * A generic type helper which removes one or more properties from an interface.
26 | */
27 | type Omit = Pick>;
28 |
29 | // Props extends { [Property in TKey]: Realm}, TKey extends keyof Props
30 |
31 | /**
32 | * Generates a withRealm function wrapping a context consumer.
33 | *
34 | * Use {@link createRealmContext} or the default withRealm instead of calling this directly.
35 | */
36 | export function generateWithRealm(
37 | Consumer: React.ComponentType,
38 | ) {
39 | // Default key is "realm"
40 | function withRealm(
41 | Component: React.ComponentType,
42 | ): React.ComponentType>;
43 | // Alternatively a key is passed as a second argument
44 | function withRealm<
45 | Props extends { [P in TKey]: Realm },
46 | TKey extends keyof Props
47 | >(
48 | Component: React.ComponentType,
49 | key: TKey,
50 | consumerProps?: Partial,
51 | ): React.ComponentType>;
52 | // Implementation doesn't care about the key
53 | function withRealm(
54 | Component: React.ComponentType,
55 | key: string = 'realm',
56 | consumerProps: Partial = {},
57 | ) {
58 | /**
59 | * [Higher order component](https://reactjs.org/docs/higher-order-components.html) enhancing the wrapped component
60 | * by injecting a Realm into its props.
61 | */
62 | return class WithRealm extends React.Component {
63 | /**
64 | * Renders the component.
65 | */
66 | public render() {
67 | return (
68 |
69 | {context => {
70 | // Inject a prop using the key supplied by the caller
71 | const injectedProps = { [key]: context.realm };
72 | return ;
73 | }}
74 |
75 | );
76 | }
77 | };
78 | }
79 | // Return the enhancing HOC
80 | return withRealm;
81 | }
82 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es6",
4 | "module": "commonjs",
5 | "jsx": "react",
6 | "noImplicitAny": true,
7 | "declaration": true,
8 | "typeRoots": ["./src/@types", "./node_modules/@types"]
9 | },
10 | "include": [
11 | "src/**/*.tsx",
12 | "src/**/*.ts",
13 | "src/**/*.d.ts",
14 | "integration-tests/*.ts"
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "tslint:latest",
4 | "tslint-react",
5 | "tslint-eslint-rules",
6 | "tslint-config-prettier"
7 | ],
8 | "rulesDirectory": [
9 | "tslint-plugin-prettier"
10 | ],
11 | "rules": {
12 | "completed-docs": [
13 | true,
14 | {
15 | "classes": { "visibilities": ["exported"] },
16 | "enums": { "visibilities": ["exported"] },
17 | "enum-members": { "visibilities": ["exported"] },
18 | "functions": { "visibilities": ["exported"] },
19 | "interfaces": { "visibilities": ["exported"] },
20 | "properties": { "privacies": ["public", "protected"] },
21 | "methods": { "privacies": ["public", "protected"] },
22 | "types": { "visibilities": ["exported"] },
23 | "variables": { "visibilities": ["exported"] }
24 | }
25 | ],
26 | "object-literal-sort-keys": false,
27 | "jsx-boolean-value": false,
28 | "jsx-no-lambda": false,
29 | "no-implicit-dependencies": [
30 | true,
31 | "dev"
32 | ],
33 | "prettier": [
34 | true,
35 | {
36 | "singleQuote": true,
37 | "trailingComma": "all"
38 | }
39 | ]
40 | }
41 | }
42 |
--------------------------------------------------------------------------------