diff --git a/404.html b/404.html index 19f7d2f8..f6d916f2 100644 --- a/404.html +++ b/404.html @@ -5,7 +5,7 @@ Page Not Found | Geo Garden Club - + diff --git a/assets/js/0058b4c6.68a1efdf.js b/assets/js/0058b4c6.756e19e2.js similarity index 70% rename from assets/js/0058b4c6.68a1efdf.js rename to assets/js/0058b4c6.756e19e2.js index bbe6d94a..2ae6a83c 100644 --- a/assets/js/0058b4c6.68a1efdf.js +++ b/assets/js/0058b4c6.756e19e2.js @@ -1 +1 @@ -"use strict";(self.webpackChunkgeogardenclub_github_io=self.webpackChunkgeogardenclub_github_io||[]).push([[4088],{6462:e=>{e.exports=JSON.parse('{"version":{"pluginId":"default","version":"current","label":"Next","banner":null,"badge":false,"noIndex":false,"className":"docs-version-current","isLast":true,"docsSidebars":{"homeSidebar":[{"type":"category","collapsed":false,"label":"About GeoGardenClub","items":[{"type":"link","label":"Welcome","href":"/docs/home/welcome","docId":"home/welcome","unlisted":false},{"type":"link","label":"\\"Serious\\" Gardeners","href":"/docs/home/serious-gardeners","docId":"home/serious-gardeners","unlisted":false},{"type":"link","label":"Design Innovations","href":"/docs/home/innovations","docId":"home/innovations","unlisted":false},{"type":"link","label":"Garden Planning Tools","href":"/docs/home/related-work","docId":"home/related-work","unlisted":false},{"type":"link","label":"Food Security","href":"/docs/home/food-security","docId":"home/food-security","unlisted":false},{"type":"link","label":"The Team","href":"/docs/home/team","docId":"home/team","unlisted":false}],"collapsible":true},{"type":"category","collapsed":false,"label":"User Guide","items":[{"type":"link","label":"Overview","href":"/docs/user-guide/overview","docId":"user-guide/overview","unlisted":false},{"type":"link","label":"Downloading","href":"/docs/user-guide/downloading","docId":"user-guide/downloading","unlisted":false},{"type":"link","label":"Registration","href":"/docs/user-guide/registration","docId":"user-guide/registration","unlisted":false},{"type":"link","label":"Define a Garden","href":"/docs/user-guide/define-a-garden","docId":"user-guide/define-a-garden","unlisted":false},{"type":"link","label":"Add Plantings to Beds","href":"/docs/user-guide/adding-plantings","docId":"user-guide/adding-plantings","unlisted":false},{"type":"link","label":"Explore a Garden","href":"/docs/user-guide/explore-a-garden","docId":"user-guide/explore-a-garden","unlisted":false},{"type":"link","label":"Explore a Chapter","href":"/docs/user-guide/explore-a-chapter","docId":"user-guide/explore-a-chapter","unlisted":false},{"type":"link","label":"Add Crops, Varieties, Vendors to the Chapter Database","href":"/docs/user-guide/adding-vendors-crops-varieties","docId":"user-guide/adding-vendors-crops-varieties","unlisted":false},{"type":"link","label":"Planting Scenarios","href":"/docs/user-guide/scenarios","docId":"user-guide/scenarios","unlisted":false},{"type":"link","label":"Observations","href":"/docs/user-guide/observations","docId":"user-guide/observations","unlisted":false},{"type":"link","label":"GeoBot","href":"/docs/user-guide/geobot","docId":"user-guide/geobot","unlisted":false},{"type":"link","label":"Badges","href":"/docs/user-guide/badges","docId":"user-guide/badges","unlisted":false},{"type":"link","label":"outcomes","href":"/docs/user-guide/outcomes","docId":"user-guide/outcomes","unlisted":false},{"type":"link","label":"Tasks","href":"/docs/user-guide/tasks","docId":"user-guide/tasks","unlisted":false},{"type":"link","label":"Seeds","href":"/docs/user-guide/seeds","docId":"user-guide/seeds","unlisted":false},{"type":"link","label":"Chat Rooms","href":"/docs/user-guide/chat-rooms","docId":"user-guide/chat-rooms","unlisted":false},{"type":"link","label":"Frequently Asked (Gardening) Questions","href":"/docs/user-guide/guided-tour","docId":"user-guide/guided-tour","unlisted":false},{"type":"link","label":"Terms and Conditions","href":"/docs/user-guide/terms-and-conditions","docId":"user-guide/terms-and-conditions","unlisted":false},{"type":"link","label":"Privacy Policy","href":"/docs/user-guide/privacy","docId":"user-guide/privacy","unlisted":false}],"collapsible":true}],"businessSidebar":[{"type":"link","label":"Welcome","href":"/docs/business/","docId":"business/index","unlisted":false},{"type":"link","label":"Roadmap","href":"/docs/business/roadmap","docId":"business/roadmap","unlisted":false},{"type":"link","label":"Milestones","href":"/docs/business/milestones","docId":"business/milestones","unlisted":false},{"type":"link","label":"Market Size","href":"/docs/business/market-size","docId":"business/market-size","unlisted":false},{"type":"link","label":"Lean Canvas","href":"https://docs.google.com/presentation/d/1oUzy1zeraTf6PgWlk2R3Ea7Iw2Ju24Dds5mffq2o5Wg/edit#slide=id.p1"},{"type":"link","label":"Gardening vs. Farming","href":"https://docs.google.com/presentation/d/1rMu7DWJblHvVJt6CGmR8eyCN7uBMXPfBpc1rdhanxjQ/edit#slide=id.p"},{"type":"link","label":"Home Gardening Pain Points","href":"https://docs.google.com/presentation/d/1TKDWQI60PxRhBpMGW0tvMyXX0nmgEBz71-DmoT2LxfU/edit#slide=id.g11d82564388_0_187"},{"type":"link","label":"BAI Pitch Deck","href":"https://www.canva.com/design/DAGRP0s8F_s/93cd6dWj_1ycGEMCVx1tjA/edit"},{"type":"link","label":"Business Documents Repo","href":"https://github.com/geogardenclub/documents"},{"type":"link","label":"Developer Guide","href":"/docs/develop/"}],"developSidebar":[{"type":"link","label":"Welcome","href":"/docs/develop/","docId":"develop/index","unlisted":false},{"type":"link","label":"Onboarding","href":"/docs/develop/onboarding","docId":"develop/onboarding","unlisted":false},{"type":"link","label":"Installation","href":"/docs/develop/installation","docId":"develop/installation","unlisted":false},{"type":"link","label":"Scripts","href":"/docs/develop/scripts","docId":"develop/scripts","unlisted":false},{"type":"link","label":"Coding Standards","href":"/docs/develop/coding-standards","docId":"develop/coding-standards","unlisted":false},{"type":"link","label":"Architecture","href":"/docs/develop/architecture","docId":"develop/architecture","unlisted":false},{"type":"link","label":"Deployment","href":"/docs/develop/deployment","docId":"develop/deployment","unlisted":false},{"type":"link","label":"Testing","href":"/docs/develop/testing","docId":"develop/testing","unlisted":false},{"type":"link","label":"Backups","href":"/docs/develop/backups","docId":"develop/backups","unlisted":false},{"type":"category","collapsed":true,"label":"Design","items":[{"type":"link","label":"Data Model","href":"/docs/develop/design/data-model","docId":"develop/design/data-model","unlisted":false},{"type":"link","label":"Badges","href":"/docs/develop/design/badges","docId":"develop/design/badges","unlisted":false},{"type":"link","label":"GGC Input Fields","href":"/docs/develop/design/input-fields","docId":"develop/design/input-fields","unlisted":false},{"type":"link","label":"\\"With\\" widgets","href":"/docs/develop/design/with-widgets","docId":"develop/design/with-widgets","unlisted":false},{"type":"link","label":"Data Mutation","href":"/docs/develop/design/data-mutation","docId":"develop/design/data-mutation","unlisted":false}],"collapsible":true},{"type":"category","collapsed":true,"label":"Prior Releases","items":[{"type":"category","label":"Release 1.0 (Technology Evaluation)","collapsed":true,"items":[{"type":"link","label":"Technology Goals","href":"/docs/develop/releases/release-1.0/goals","docId":"develop/releases/release-1.0/goals","unlisted":false},{"type":"link","label":"Core Value Propositions","href":"/docs/develop/releases/release-1.0/cvp","docId":"develop/releases/release-1.0/cvp","unlisted":false},{"type":"link","label":"Onboarding Feedback","href":"/docs/develop/releases/release-1.0/onboarding-feedback","docId":"develop/releases/release-1.0/onboarding-feedback","unlisted":false},{"type":"link","label":"End of Season Feedback","href":"/docs/develop/releases/release-1.0/end-of-season-feedback","docId":"develop/releases/release-1.0/end-of-season-feedback","unlisted":false}],"collapsible":true},{"type":"category","collapsed":true,"label":"Release 0.0 (Mockup)","items":[{"type":"link","label":"Design and implementation","href":"/docs/develop/releases/release-0.0/design","docId":"develop/releases/release-0.0/design","unlisted":false},{"type":"link","label":"Customer feedback","href":"/docs/develop/releases/release-0.0/customer-feedback","docId":"develop/releases/release-0.0/customer-feedback","unlisted":false},{"type":"link","label":"Entrepreneur feedback","href":"/docs/develop/releases/release-0.0/entrepreneur-feedback","docId":"develop/releases/release-0.0/entrepreneur-feedback","unlisted":false},{"type":"link","label":"ChatGPT feedback","href":"/docs/develop/releases/release-0.0/chatgpt-feedback","docId":"develop/releases/release-0.0/chatgpt-feedback","unlisted":false}],"collapsible":true}],"collapsible":true},{"type":"link","label":"Business Development Guide","href":"/docs/business/"}]},"docs":{"business/index":{"id":"business/index","title":"Welcome to the GGC Business Development Guide","description":"Welcome to the Business Development Guide for the Geo Garden Club project.","sidebar":"businessSidebar"},"business/market-size":{"id":"business/market-size","title":"Market Size Estimation (USA)","description":"Here is some data that can help provide a sense for the potential market size for GGC in the United States.","sidebar":"businessSidebar"},"business/milestones":{"id":"business/milestones","title":"Milestones","description":"| Year | Date | Milestone |","sidebar":"businessSidebar"},"business/roadmap":{"id":"business/roadmap","title":"Roadmap","description":"This roadmap documents our approach incremental development and release of our technology.","sidebar":"businessSidebar"},"develop/architecture":{"id":"develop/architecture","title":"Architecture","description":"The GeoGardenClub app (GGC) conforms (most of the time) to the architectural approach advocated by Andreas Bizzotto which he calls the \\"Riverpod Architecture\\". If you are not familiar with this approach, it\'s worth spending a few minutes reading through his description, which is available as a set of readings in the architecture module in my mobile application development course.","sidebar":"developSidebar"},"develop/backups":{"id":"develop/backups","title":"Backups","description":"Our current backup approach is to use Firefoo to create a JSON file containing all of the documents in the GGC Firestore database, compress this file, and upload it to the geogardenclub/backups repository. The goal is to do this every week or two, so that in the event of catastrophe, we can restore the database to a state that doesn\'t lose too much work.","sidebar":"developSidebar"},"develop/coding-standards":{"id":"develop/coding-standards","title":"Coding Standards","description":"In GGC, coding standards are similar to design patterns, but focus on practices that reduce or avoid \\"technical debt\\".","sidebar":"developSidebar"},"develop/deployment":{"id":"develop/deployment","title":"Deployment","description":"For the GeoGardenClub project, deployment refers to the process by which a version of the GeoGardenClub app is made available on a physical device such as an Apple or Android phone or tablet.","sidebar":"developSidebar"},"develop/design/badges":{"id":"develop/design/badges","title":"Badges","description":"Goals","sidebar":"developSidebar"},"develop/design/data-model":{"id":"develop/design/data-model","title":"Data Model","description":"This page explains the data model (i.e. the set of entities and their relationships) for GGC, along with a rationale for the design decisions that we\'ve made along the way.","sidebar":"developSidebar"},"develop/design/data-model-old":{"id":"develop/design/data-model-old","title":"Data Model","description":"This page documents the data model intended to satisfy the 1.0 release requirements."},"develop/design/data-mutation":{"id":"develop/design/data-mutation","title":"Data Mutation","description":"Prelude: AsyncValue","sidebar":"developSidebar"},"develop/design/input-fields":{"id":"develop/design/input-fields","title":"GGC Input Fields","description":"Motivation","sidebar":"developSidebar"},"develop/design/with-widgets":{"id":"develop/design/with-widgets","title":"\\"With\\" widgets","description":"Why \\"with\\"?","sidebar":"developSidebar"},"develop/index":{"id":"develop/index","title":"Welcome to the GGC Developers Guide","description":"Welcome to the Developer\'s Guide for the Geo Garden Club project.","sidebar":"developSidebar"},"develop/installation":{"id":"develop/installation","title":"Installation","description":"Flutter","sidebar":"developSidebar"},"develop/onboarding":{"id":"develop/onboarding","title":"Onboarding","description":"Welcome, new GGC developer! This page provides a checklist of things required to get started with our technology.","sidebar":"developSidebar"},"develop/releases/release-0.0/chatgpt-feedback":{"id":"develop/releases/release-0.0/chatgpt-feedback","title":"ChatGPT feedback","description":"Just for fun, I had a conversation with ChatGPT in April 2023 about how to design a gardening app. Much of its responses were uninsightful, but it did come up with some banging ideas for badges.","sidebar":"developSidebar"},"develop/releases/release-0.0/customer-feedback":{"id":"develop/releases/release-0.0/customer-feedback","title":"Customer feedback","description":"Following the Lean Startup principle of \\"validated learning\\", we performed an evaluation study of the technology innovations present in the mockup with 24 experienced gardeners during the summer and fall of 2022. We report on the results of that study in the following 15 minute video (or 7 minutes, if you run the video at 2x speed):","sidebar":"developSidebar"},"develop/releases/release-0.0/design":{"id":"develop/releases/release-0.0/design","title":"Design and implementation","description":"In 2022, we built a simple single page web application using React. The goal of the system was to provide an executable mockup to perform \\"customer discovery\\". The mockup showed potential users our vision of some of the features that would be made available in our production technology, and enabled us to gather feedback on the potential utility and value of our vision.","sidebar":"developSidebar"},"develop/releases/release-0.0/entrepreneur-feedback":{"id":"develop/releases/release-0.0/entrepreneur-feedback","title":"Entrepreneur feedback","description":"The following sections document feedback received from entrepreneurs and high tech professionals (who are not necessarily gardeners, and thus not in our customer demographics). Our request to these folks was to evaluate the viability of Agile/Geo Garden Club as a commercial technology venture, and to help us identify opportunities for improvement. We got a lot of useful feedback.","sidebar":"developSidebar"},"develop/releases/release-1.0/cvp":{"id":"develop/releases/release-1.0/cvp","title":"Core Value Propositions","description":"The goal of the 1.0 (Technology Evaluation) release is to provide an app to a small group that will use the app and provide us with feedback. The 1.0 release will partially test our business model by helping us evaluate its success at implementing the \\"core value propositions\\" (CVPs) for GGC.","sidebar":"developSidebar"},"develop/releases/release-1.0/end-of-season-feedback":{"id":"develop/releases/release-1.0/end-of-season-feedback","title":"End of Season Feedback","description":"In September 2024, Jenna sent out an 8 question survey to users to request feedback. We received five responses. Here is a summary of the feedback:","sidebar":"developSidebar"},"develop/releases/release-1.0/goals":{"id":"develop/releases/release-1.0/goals","title":"Technology Goals","description":"Here are the goals for the 1.0 (Technology Evaluation) release. Some of these goals are motivated by Champion Building: How to successfully adopt a developer tool. Although this blog post focuses on how to get developers in an organization to adopt a new tool, the recommendation seem very applicable to getting gardeners in a community to adopt GGC.","sidebar":"developSidebar"},"develop/releases/release-1.0/onboarding-feedback":{"id":"develop/releases/release-1.0/onboarding-feedback","title":"Onboarding Feedback","description":"Jenna personally onboarded six users to Release 1.0 and gathered the following feedback from them either during the onboarding session or shortly thereafter.","sidebar":"developSidebar"},"develop/scripts":{"id":"develop/scripts","title":"Scripts","description":"GGC development is supported by a number of Unix shell scripts. All scripts are named starting with \\"run\\" and use snake case to separate words in the script name.","sidebar":"developSidebar"},"develop/testing":{"id":"develop/testing","title":"Testing","description":"This page documents our current approach to testing the GeoGardenClub app.","sidebar":"developSidebar"},"home/food-security":{"id":"home/food-security","title":"Food Security","description":"Food security is the elephant in the room, and a major motivation for founding GeoGardenClub. Here is a brief introduction to the relationship between food security and home gardens.","sidebar":"homeSidebar"},"home/innovations":{"id":"home/innovations","title":"Design Innovations","description":"What should an app provide if it is intended to support the needs of \\"serious\\" gardeners? Here is GeoGardenClub\'s answer:","sidebar":"homeSidebar"},"home/related-work":{"id":"home/related-work","title":"Garden Planning Tools","description":"If you search for \\"garden planning tools\\" on the Internet, you\'ll find dozens of applications. Most of those are essentially \\"landscape architecture\\" tools for people who want to design the visual look of their (flower) gardens. This is an interesting design problem, but not the problem addressed by Geo Garden Club.","sidebar":"homeSidebar"},"home/serious-gardeners":{"id":"home/serious-gardeners","title":"\\"Serious\\" Gardeners","description":"The founders of GeoGardenClub view food production as a vast continuum of activities and levels of commitment, as shown in the following diagram:","sidebar":"homeSidebar"},"home/sneak-peek":{"id":"home/sneak-peek","title":"Mobile App Sneak Peek","description":"We are working on the alpha release of the GeoGardenClub mobile app, with an expected release date of early 2024. While the app is not yet ready for prime time, we thought it would be fun to show you some selected screen shots so you can get an idea of where we\'re heading."},"home/team":{"id":"home/team","title":"The Team","description":"Jenna Deane","sidebar":"homeSidebar"},"home/welcome":{"id":"home/welcome","title":"Welcome","description":"GGC started in 2021 when a small group of gardeners in Bellingham, Washington realized that, despite the availability of dozens of home gardening apps, we were planning and managing our gardens using spreadsheets, Word docs, and even paper and pencil!","sidebar":"homeSidebar"},"user-guide/adding-plantings":{"id":"user-guide/adding-plantings","title":"Add Plantings to Beds","description":"Add plantings","sidebar":"homeSidebar"},"user-guide/adding-vendors-crops-varieties":{"id":"user-guide/adding-vendors-crops-varieties","title":"Add Crops, Varieties, Vendors to the Chapter Database","description":"The crops, varieties, and vendors in each chapter\'s database are crowdsourced from the chapter\'s members. This ensures that the database is exclusive to what is grown in the area. To add a crop, variety, or vendor navigate to the appropriate index screen from the side navigation menu.","sidebar":"homeSidebar"},"user-guide/badges":{"id":"user-guide/badges","title":"Badges","description":"Why Badges?","sidebar":"homeSidebar"},"user-guide/chat-rooms":{"id":"user-guide/chat-rooms","title":"Chat Rooms","description":"GGC allows gardeners to communicate with each other through chat rooms. Chat rooms are a great way to ask questions, share knowledge, and build community. Chat rooms are organized by chapter or garden.","sidebar":"homeSidebar"},"user-guide/define-a-garden":{"id":"user-guide/define-a-garden","title":"Define a Garden","description":"Defining a garden in GeoGardenClub requires you to create three kinds of entities: a \\"Garden\\", one or more \\"Beds\\", and one or more \\"Plantings\\". (You can also define a fourth type of entity, \\"Outcomes\\", but we\'ll get to that later).","sidebar":"homeSidebar"},"user-guide/downloading":{"id":"user-guide/downloading","title":"Downloading","description":"Enroll as a GGC Beta Tester","sidebar":"homeSidebar"},"user-guide/explore-a-chapter":{"id":"user-guide/explore-a-chapter","title":"Explore a Chapter","description":"Find the Chapter Index Screens","sidebar":"homeSidebar"},"user-guide/explore-a-garden":{"id":"user-guide/explore-a-garden","title":"Explore a Garden","description":"The summary card for every Garden in a Chapter contains a button labeled \\"Details\\". Pressing that button takes you to a screen with a bottom nav bar containing icons taking you to four screens: Timeline, Filter, Outcomes, and Tasks. Each view presents a useful perspective on that Garden.","sidebar":"homeSidebar"},"user-guide/geobot":{"id":"user-guide/geobot","title":"GeoBot","description":"Why GeoBot?","sidebar":"homeSidebar"},"user-guide/guided-tour":{"id":"user-guide/guided-tour","title":"Frequently Asked (Gardening) Questions","description":"GeoGardenClub was created to increase the performance of home, community, and school gardens and efficiency of the gardener by answering questions a gardener might have as they plan or work through the gardening season. Here are some of the questions that GeoGardenClub can help answer:","sidebar":"homeSidebar"},"user-guide/observations":{"id":"user-guide/observations","title":"Observations","description":"GGC allows gardeners to make \\"observations\\" regarding a planting of a plant variety on a specific day. Observations can include phenomena such as successful germination, first flower, first harvest, diseases, or pests, document outcomes like yields, or simply record the progress of a planting.","sidebar":"homeSidebar"},"user-guide/outcomes":{"id":"user-guide/outcomes","title":"outcomes","description":"300---","sidebar":"homeSidebar"},"user-guide/overview":{"id":"user-guide/overview","title":"Overview","description":"Welcome to the User Guide for the GeoGardenClub app.","sidebar":"homeSidebar"},"user-guide/privacy":{"id":"user-guide/privacy","title":"Privacy Policy","description":"Last updated June 20, 2024","sidebar":"homeSidebar"},"user-guide/registration":{"id":"user-guide/registration","title":"Registration","description":"After downloading the app, open it to see the login screen. Tap Register to create a new account.","sidebar":"homeSidebar"},"user-guide/scenarios":{"id":"user-guide/scenarios","title":"Planting Scenarios","description":"Growing plants in a greenhouse or other climate controlled environment","sidebar":"homeSidebar"},"user-guide/seeds":{"id":"user-guide/seeds","title":"Seeds","description":"Geo Garden Club believes that seed saving and sharing is a critical component of building resilient gardening communities. By saving seeds, gardeners can ensure that they have access to the varieties that grow best in their area. By sharing seeds, gardeners can help others in their community grow the same varieties.","sidebar":"homeSidebar"},"user-guide/tasks":{"id":"user-guide/tasks","title":"Tasks","description":"What are Tasks?","sidebar":"homeSidebar"},"user-guide/terms-and-conditions":{"id":"user-guide/terms-and-conditions","title":"Terms and Conditions","description":"Last updated February 10, 2024","sidebar":"homeSidebar"}}}}')}}]); \ No newline at end of file +"use strict";(self.webpackChunkgeogardenclub_github_io=self.webpackChunkgeogardenclub_github_io||[]).push([[4088],{6462:e=>{e.exports=JSON.parse('{"version":{"pluginId":"default","version":"current","label":"Next","banner":null,"badge":false,"noIndex":false,"className":"docs-version-current","isLast":true,"docsSidebars":{"homeSidebar":[{"type":"category","collapsed":false,"label":"About GeoGardenClub","items":[{"type":"link","label":"Welcome","href":"/docs/home/welcome","docId":"home/welcome","unlisted":false},{"type":"link","label":"\\"Serious\\" Gardeners","href":"/docs/home/serious-gardeners","docId":"home/serious-gardeners","unlisted":false},{"type":"link","label":"Design Innovations","href":"/docs/home/innovations","docId":"home/innovations","unlisted":false},{"type":"link","label":"Garden Planning Tools","href":"/docs/home/related-work","docId":"home/related-work","unlisted":false},{"type":"link","label":"Food Security","href":"/docs/home/food-security","docId":"home/food-security","unlisted":false},{"type":"link","label":"The Team","href":"/docs/home/team","docId":"home/team","unlisted":false}],"collapsible":true},{"type":"category","collapsed":false,"label":"User Guide","items":[{"type":"link","label":"Overview","href":"/docs/user-guide/overview","docId":"user-guide/overview","unlisted":false},{"type":"link","label":"Downloading","href":"/docs/user-guide/downloading","docId":"user-guide/downloading","unlisted":false},{"type":"link","label":"Registration","href":"/docs/user-guide/registration","docId":"user-guide/registration","unlisted":false},{"type":"link","label":"Define a Garden","href":"/docs/user-guide/define-a-garden","docId":"user-guide/define-a-garden","unlisted":false},{"type":"link","label":"Add Plantings to Beds","href":"/docs/user-guide/adding-plantings","docId":"user-guide/adding-plantings","unlisted":false},{"type":"link","label":"Explore a Garden","href":"/docs/user-guide/explore-a-garden","docId":"user-guide/explore-a-garden","unlisted":false},{"type":"link","label":"Explore a Chapter","href":"/docs/user-guide/explore-a-chapter","docId":"user-guide/explore-a-chapter","unlisted":false},{"type":"link","label":"Add Crops, Varieties, Vendors to the Chapter Database","href":"/docs/user-guide/adding-vendors-crops-varieties","docId":"user-guide/adding-vendors-crops-varieties","unlisted":false},{"type":"link","label":"Planting Scenarios","href":"/docs/user-guide/scenarios","docId":"user-guide/scenarios","unlisted":false},{"type":"link","label":"Observations","href":"/docs/user-guide/observations","docId":"user-guide/observations","unlisted":false},{"type":"link","label":"GeoBot","href":"/docs/user-guide/geobot","docId":"user-guide/geobot","unlisted":false},{"type":"link","label":"Badges","href":"/docs/user-guide/badges","docId":"user-guide/badges","unlisted":false},{"type":"link","label":"outcomes","href":"/docs/user-guide/outcomes","docId":"user-guide/outcomes","unlisted":false},{"type":"link","label":"Tasks","href":"/docs/user-guide/tasks","docId":"user-guide/tasks","unlisted":false},{"type":"link","label":"Seeds","href":"/docs/user-guide/seeds","docId":"user-guide/seeds","unlisted":false},{"type":"link","label":"Chat Rooms","href":"/docs/user-guide/chat-rooms","docId":"user-guide/chat-rooms","unlisted":false},{"type":"link","label":"Frequently Asked (Gardening) Questions","href":"/docs/user-guide/guided-tour","docId":"user-guide/guided-tour","unlisted":false},{"type":"link","label":"Terms and Conditions","href":"/docs/user-guide/terms-and-conditions","docId":"user-guide/terms-and-conditions","unlisted":false},{"type":"link","label":"Privacy Policy","href":"/docs/user-guide/privacy","docId":"user-guide/privacy","unlisted":false}],"collapsible":true}],"businessSidebar":[{"type":"link","label":"Welcome","href":"/docs/business/","docId":"business/index","unlisted":false},{"type":"link","label":"Roadmap","href":"/docs/business/roadmap","docId":"business/roadmap","unlisted":false},{"type":"link","label":"Milestones","href":"/docs/business/milestones","docId":"business/milestones","unlisted":false},{"type":"link","label":"Market Size","href":"/docs/business/market-size","docId":"business/market-size","unlisted":false},{"type":"link","label":"Lean Canvas","href":"https://docs.google.com/presentation/d/1oUzy1zeraTf6PgWlk2R3Ea7Iw2Ju24Dds5mffq2o5Wg/edit#slide=id.p1"},{"type":"link","label":"Gardening vs. Farming","href":"https://docs.google.com/presentation/d/1rMu7DWJblHvVJt6CGmR8eyCN7uBMXPfBpc1rdhanxjQ/edit#slide=id.p"},{"type":"link","label":"Home Gardening Pain Points","href":"https://docs.google.com/presentation/d/1TKDWQI60PxRhBpMGW0tvMyXX0nmgEBz71-DmoT2LxfU/edit#slide=id.g11d82564388_0_187"},{"type":"link","label":"BAI Pitch Deck","href":"https://www.canva.com/design/DAGRP0s8F_s/93cd6dWj_1ycGEMCVx1tjA/edit"},{"type":"link","label":"Business Documents Repo","href":"https://github.com/geogardenclub/documents"},{"type":"link","label":"Developer Guide","href":"/docs/develop/"}],"developSidebar":[{"type":"link","label":"Welcome","href":"/docs/develop/","docId":"develop/index","unlisted":false},{"type":"link","label":"Onboarding","href":"/docs/develop/onboarding","docId":"develop/onboarding","unlisted":false},{"type":"link","label":"Installation","href":"/docs/develop/installation","docId":"develop/installation","unlisted":false},{"type":"link","label":"Scripts","href":"/docs/develop/scripts","docId":"develop/scripts","unlisted":false},{"type":"link","label":"Coding Standards","href":"/docs/develop/coding-standards","docId":"develop/coding-standards","unlisted":false},{"type":"link","label":"Architecture","href":"/docs/develop/architecture","docId":"develop/architecture","unlisted":false},{"type":"link","label":"Deployment","href":"/docs/develop/deployment","docId":"develop/deployment","unlisted":false},{"type":"link","label":"Testing","href":"/docs/develop/testing","docId":"develop/testing","unlisted":false},{"type":"link","label":"Backups","href":"/docs/develop/backups","docId":"develop/backups","unlisted":false},{"type":"category","collapsed":true,"label":"Design","items":[{"type":"link","label":"Data Model","href":"/docs/develop/design/data-model","docId":"develop/design/data-model","unlisted":false},{"type":"link","label":"Badges","href":"/docs/develop/design/badges","docId":"develop/design/badges","unlisted":false},{"type":"link","label":"GGC Input Fields","href":"/docs/develop/design/input-fields","docId":"develop/design/input-fields","unlisted":false},{"type":"link","label":"\\"With\\" widgets","href":"/docs/develop/design/with-widgets","docId":"develop/design/with-widgets","unlisted":false},{"type":"link","label":"Data Mutation","href":"/docs/develop/design/data-mutation","docId":"develop/design/data-mutation","unlisted":false}],"collapsible":true},{"type":"category","collapsed":true,"label":"Prior Releases","items":[{"type":"category","label":"Release 1.0 (Technology Evaluation)","collapsed":true,"items":[{"type":"link","label":"Technology Goals","href":"/docs/develop/releases/release-1.0/goals","docId":"develop/releases/release-1.0/goals","unlisted":false},{"type":"link","label":"Core Value Propositions","href":"/docs/develop/releases/release-1.0/cvp","docId":"develop/releases/release-1.0/cvp","unlisted":false},{"type":"link","label":"Onboarding Feedback","href":"/docs/develop/releases/release-1.0/onboarding-feedback","docId":"develop/releases/release-1.0/onboarding-feedback","unlisted":false},{"type":"link","label":"End of Season Feedback","href":"/docs/develop/releases/release-1.0/end-of-season-feedback","docId":"develop/releases/release-1.0/end-of-season-feedback","unlisted":false}],"collapsible":true},{"type":"category","collapsed":true,"label":"Release 0.0 (Mockup)","items":[{"type":"link","label":"Design and implementation","href":"/docs/develop/releases/release-0.0/design","docId":"develop/releases/release-0.0/design","unlisted":false},{"type":"link","label":"Customer feedback","href":"/docs/develop/releases/release-0.0/customer-feedback","docId":"develop/releases/release-0.0/customer-feedback","unlisted":false},{"type":"link","label":"Entrepreneur feedback","href":"/docs/develop/releases/release-0.0/entrepreneur-feedback","docId":"develop/releases/release-0.0/entrepreneur-feedback","unlisted":false},{"type":"link","label":"ChatGPT feedback","href":"/docs/develop/releases/release-0.0/chatgpt-feedback","docId":"develop/releases/release-0.0/chatgpt-feedback","unlisted":false}],"collapsible":true}],"collapsible":true},{"type":"link","label":"Business Development Guide","href":"/docs/business/"}]},"docs":{"business/index":{"id":"business/index","title":"Welcome to the GGC Business Development Guide","description":"Welcome to the Business Development Guide for the Geo Garden Club project.","sidebar":"businessSidebar"},"business/market-size":{"id":"business/market-size","title":"Market Size Estimation (USA)","description":"Here is some data that can help provide a sense for the potential market size for GGC in the United States.","sidebar":"businessSidebar"},"business/milestones":{"id":"business/milestones","title":"Milestones","description":"| Year | Date | Milestone |","sidebar":"businessSidebar"},"business/roadmap":{"id":"business/roadmap","title":"Roadmap","description":"This roadmap documents our approach incremental development and release of our technology.","sidebar":"businessSidebar"},"develop/architecture":{"id":"develop/architecture","title":"Architecture","description":"The GeoGardenClub app (GGC) conforms (most of the time) to the architectural approach advocated by Andreas Bizzotto which he calls the \\"Riverpod Architecture\\". If you are not familiar with this approach, it\'s worth spending a few minutes reading through his description, which is available as a set of readings in the architecture module in my mobile application development course.","sidebar":"developSidebar"},"develop/backups":{"id":"develop/backups","title":"Backups","description":"Our current backup approach is to use Firefoo to create a JSON file containing all of the documents in the GGC Firestore database, compress this file, and upload it to the geogardenclub/backups repository. The goal is to do this every week or two, so that in the event of catastrophe, we can restore the database to a state that doesn\'t lose too much work.","sidebar":"developSidebar"},"develop/coding-standards":{"id":"develop/coding-standards","title":"Coding Standards","description":"In GGC, coding standards are similar to design patterns, but focus on practices that reduce or avoid \\"technical debt\\".","sidebar":"developSidebar"},"develop/deployment":{"id":"develop/deployment","title":"Deployment","description":"For the GeoGardenClub project, deployment refers to the process by which a version of the GeoGardenClub app is made available on a physical device such as an Apple or Android phone or tablet.","sidebar":"developSidebar"},"develop/design/badges":{"id":"develop/design/badges","title":"Badges","description":"Goals","sidebar":"developSidebar"},"develop/design/data-model":{"id":"develop/design/data-model","title":"Data Model","description":"This page explains the data model (i.e. the set of entities and their relationships) for GGC, along with a rationale for the design decisions that we\'ve made along the way.","sidebar":"developSidebar"},"develop/design/data-model-old":{"id":"develop/design/data-model-old","title":"Data Model","description":"This page documents the data model intended to satisfy the 1.0 release requirements."},"develop/design/data-mutation":{"id":"develop/design/data-mutation","title":"Data Mutation","description":"Prelude: AsyncValue","sidebar":"developSidebar"},"develop/design/input-fields":{"id":"develop/design/input-fields","title":"GGC Input Fields","description":"Motivation","sidebar":"developSidebar"},"develop/design/with-widgets":{"id":"develop/design/with-widgets","title":"\\"With\\" widgets","description":"Why \\"with\\"?","sidebar":"developSidebar"},"develop/index":{"id":"develop/index","title":"Welcome to the GGC Developers Guide","description":"Welcome to the Developer\'s Guide for the Geo Garden Club project.","sidebar":"developSidebar"},"develop/installation":{"id":"develop/installation","title":"Installation","description":"Flutter","sidebar":"developSidebar"},"develop/onboarding":{"id":"develop/onboarding","title":"Onboarding","description":"Welcome, new GGC developer! This page provides a checklist of things required to get started with our technology.","sidebar":"developSidebar"},"develop/releases/release-0.0/chatgpt-feedback":{"id":"develop/releases/release-0.0/chatgpt-feedback","title":"ChatGPT feedback","description":"Just for fun, I had a conversation with ChatGPT in April 2023 about how to design a gardening app. Much of its responses were uninsightful, but it did come up with some banging ideas for badges.","sidebar":"developSidebar"},"develop/releases/release-0.0/customer-feedback":{"id":"develop/releases/release-0.0/customer-feedback","title":"Customer feedback","description":"Following the Lean Startup principle of \\"validated learning\\", we performed an evaluation study of the technology innovations present in the mockup with 24 experienced gardeners during the summer and fall of 2022. We report on the results of that study in the following 15 minute video (or 7 minutes, if you run the video at 2x speed):","sidebar":"developSidebar"},"develop/releases/release-0.0/design":{"id":"develop/releases/release-0.0/design","title":"Design and implementation","description":"In 2022, we built a simple single page web application using React. The goal of the system was to provide an executable mockup to perform \\"customer discovery\\". The mockup showed potential users our vision of some of the features that would be made available in our production technology, and enabled us to gather feedback on the potential utility and value of our vision.","sidebar":"developSidebar"},"develop/releases/release-0.0/entrepreneur-feedback":{"id":"develop/releases/release-0.0/entrepreneur-feedback","title":"Entrepreneur feedback","description":"The following sections document feedback received from entrepreneurs and high tech professionals (who are not necessarily gardeners, and thus not in our customer demographics). Our request to these folks was to evaluate the viability of Agile/Geo Garden Club as a commercial technology venture, and to help us identify opportunities for improvement. We got a lot of useful feedback.","sidebar":"developSidebar"},"develop/releases/release-1.0/cvp":{"id":"develop/releases/release-1.0/cvp","title":"Core Value Propositions","description":"The goal of the 1.0 (Technology Evaluation) release is to provide an app to a small group that will use the app and provide us with feedback. The 1.0 release will partially test our business model by helping us evaluate its success at implementing the \\"core value propositions\\" (CVPs) for GGC.","sidebar":"developSidebar"},"develop/releases/release-1.0/end-of-season-feedback":{"id":"develop/releases/release-1.0/end-of-season-feedback","title":"End of Season Feedback","description":"In September 2024, Jenna sent out an 8 question survey to users to request feedback. We received five responses. Here is a summary of the feedback:","sidebar":"developSidebar"},"develop/releases/release-1.0/goals":{"id":"develop/releases/release-1.0/goals","title":"Technology Goals","description":"Here are the goals for the 1.0 (Technology Evaluation) release. Some of these goals are motivated by Champion Building: How to successfully adopt a developer tool. Although this blog post focuses on how to get developers in an organization to adopt a new tool, the recommendation seem very applicable to getting gardeners in a community to adopt GGC.","sidebar":"developSidebar"},"develop/releases/release-1.0/onboarding-feedback":{"id":"develop/releases/release-1.0/onboarding-feedback","title":"Onboarding Feedback","description":"Jenna personally onboarded six users to Release 1.0 and gathered the following feedback from them either during the onboarding session or shortly thereafter.","sidebar":"developSidebar"},"develop/scripts":{"id":"develop/scripts","title":"Scripts","description":"GGC development is supported by a number of Unix shell scripts. All scripts are named starting with \\"run\\" and use snake case to separate words in the script name.","sidebar":"developSidebar"},"develop/testing":{"id":"develop/testing","title":"Testing","description":"The current goal of testing in GeoGardenClub is to prevent catastrophic regression. In other words, we want our tests to ensure that changes to the code do not result in an app where important features no longer work. This means that our test suite should ensure that:","sidebar":"developSidebar"},"home/food-security":{"id":"home/food-security","title":"Food Security","description":"Food security is the elephant in the room, and a major motivation for founding GeoGardenClub. Here is a brief introduction to the relationship between food security and home gardens.","sidebar":"homeSidebar"},"home/innovations":{"id":"home/innovations","title":"Design Innovations","description":"What should an app provide if it is intended to support the needs of \\"serious\\" gardeners? Here is GeoGardenClub\'s answer:","sidebar":"homeSidebar"},"home/related-work":{"id":"home/related-work","title":"Garden Planning Tools","description":"If you search for \\"garden planning tools\\" on the Internet, you\'ll find dozens of applications. Most of those are essentially \\"landscape architecture\\" tools for people who want to design the visual look of their (flower) gardens. This is an interesting design problem, but not the problem addressed by Geo Garden Club.","sidebar":"homeSidebar"},"home/serious-gardeners":{"id":"home/serious-gardeners","title":"\\"Serious\\" Gardeners","description":"The founders of GeoGardenClub view food production as a vast continuum of activities and levels of commitment, as shown in the following diagram:","sidebar":"homeSidebar"},"home/sneak-peek":{"id":"home/sneak-peek","title":"Mobile App Sneak Peek","description":"We are working on the alpha release of the GeoGardenClub mobile app, with an expected release date of early 2024. While the app is not yet ready for prime time, we thought it would be fun to show you some selected screen shots so you can get an idea of where we\'re heading."},"home/team":{"id":"home/team","title":"The Team","description":"Jenna Deane","sidebar":"homeSidebar"},"home/welcome":{"id":"home/welcome","title":"Welcome","description":"GGC started in 2021 when a small group of gardeners in Bellingham, Washington realized that, despite the availability of dozens of home gardening apps, we were planning and managing our gardens using spreadsheets, Word docs, and even paper and pencil!","sidebar":"homeSidebar"},"user-guide/adding-plantings":{"id":"user-guide/adding-plantings","title":"Add Plantings to Beds","description":"Add plantings","sidebar":"homeSidebar"},"user-guide/adding-vendors-crops-varieties":{"id":"user-guide/adding-vendors-crops-varieties","title":"Add Crops, Varieties, Vendors to the Chapter Database","description":"The crops, varieties, and vendors in each chapter\'s database are crowdsourced from the chapter\'s members. This ensures that the database is exclusive to what is grown in the area. To add a crop, variety, or vendor navigate to the appropriate index screen from the side navigation menu.","sidebar":"homeSidebar"},"user-guide/badges":{"id":"user-guide/badges","title":"Badges","description":"Why Badges?","sidebar":"homeSidebar"},"user-guide/chat-rooms":{"id":"user-guide/chat-rooms","title":"Chat Rooms","description":"GGC allows gardeners to communicate with each other through chat rooms. Chat rooms are a great way to ask questions, share knowledge, and build community. Chat rooms are organized by chapter or garden.","sidebar":"homeSidebar"},"user-guide/define-a-garden":{"id":"user-guide/define-a-garden","title":"Define a Garden","description":"Defining a garden in GeoGardenClub requires you to create three kinds of entities: a \\"Garden\\", one or more \\"Beds\\", and one or more \\"Plantings\\". (You can also define a fourth type of entity, \\"Outcomes\\", but we\'ll get to that later).","sidebar":"homeSidebar"},"user-guide/downloading":{"id":"user-guide/downloading","title":"Downloading","description":"Enroll as a GGC Beta Tester","sidebar":"homeSidebar"},"user-guide/explore-a-chapter":{"id":"user-guide/explore-a-chapter","title":"Explore a Chapter","description":"Find the Chapter Index Screens","sidebar":"homeSidebar"},"user-guide/explore-a-garden":{"id":"user-guide/explore-a-garden","title":"Explore a Garden","description":"The summary card for every Garden in a Chapter contains a button labeled \\"Details\\". Pressing that button takes you to a screen with a bottom nav bar containing icons taking you to four screens: Timeline, Filter, Outcomes, and Tasks. Each view presents a useful perspective on that Garden.","sidebar":"homeSidebar"},"user-guide/geobot":{"id":"user-guide/geobot","title":"GeoBot","description":"Why GeoBot?","sidebar":"homeSidebar"},"user-guide/guided-tour":{"id":"user-guide/guided-tour","title":"Frequently Asked (Gardening) Questions","description":"GeoGardenClub was created to increase the performance of home, community, and school gardens and efficiency of the gardener by answering questions a gardener might have as they plan or work through the gardening season. Here are some of the questions that GeoGardenClub can help answer:","sidebar":"homeSidebar"},"user-guide/observations":{"id":"user-guide/observations","title":"Observations","description":"GGC allows gardeners to make \\"observations\\" regarding a planting of a plant variety on a specific day. Observations can include phenomena such as successful germination, first flower, first harvest, diseases, or pests, document outcomes like yields, or simply record the progress of a planting.","sidebar":"homeSidebar"},"user-guide/outcomes":{"id":"user-guide/outcomes","title":"outcomes","description":"300---","sidebar":"homeSidebar"},"user-guide/overview":{"id":"user-guide/overview","title":"Overview","description":"Welcome to the User Guide for the GeoGardenClub app.","sidebar":"homeSidebar"},"user-guide/privacy":{"id":"user-guide/privacy","title":"Privacy Policy","description":"Last updated June 20, 2024","sidebar":"homeSidebar"},"user-guide/registration":{"id":"user-guide/registration","title":"Registration","description":"After downloading the app, open it to see the login screen. Tap Register to create a new account.","sidebar":"homeSidebar"},"user-guide/scenarios":{"id":"user-guide/scenarios","title":"Planting Scenarios","description":"Growing plants in a greenhouse or other climate controlled environment","sidebar":"homeSidebar"},"user-guide/seeds":{"id":"user-guide/seeds","title":"Seeds","description":"Geo Garden Club believes that seed saving and sharing is a critical component of building resilient gardening communities. By saving seeds, gardeners can ensure that they have access to the varieties that grow best in their area. By sharing seeds, gardeners can help others in their community grow the same varieties.","sidebar":"homeSidebar"},"user-guide/tasks":{"id":"user-guide/tasks","title":"Tasks","description":"What are Tasks?","sidebar":"homeSidebar"},"user-guide/terms-and-conditions":{"id":"user-guide/terms-and-conditions","title":"Terms and Conditions","description":"Last updated February 10, 2024","sidebar":"homeSidebar"}}}}')}}]); \ No newline at end of file diff --git a/assets/js/1e5c498d.8ef05fb3.js b/assets/js/1e5c498d.8ef05fb3.js new file mode 100644 index 00000000..e520f3b1 --- /dev/null +++ b/assets/js/1e5c498d.8ef05fb3.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkgeogardenclub_github_io=self.webpackChunkgeogardenclub_github_io||[]).push([[1217],{798:(e,t,i)=>{i.r(t),i.d(t,{assets:()=>d,contentTitle:()=>a,default:()=>c,frontMatter:()=>r,metadata:()=>o,toc:()=>l});var n=i(5893),s=i(1151);const r={hide_table_of_contents:!1},a="Testing",o={id:"develop/testing",title:"Testing",description:"The current goal of testing in GeoGardenClub is to prevent catastrophic regression. In other words, we want our tests to ensure that changes to the code do not result in an app where important features no longer work. This means that our test suite should ensure that:",source:"@site/docs/develop/testing.md",sourceDirName:"develop",slug:"/develop/testing",permalink:"/docs/develop/testing",draft:!1,unlisted:!1,tags:[],version:"current",frontMatter:{hide_table_of_contents:!1},sidebar:"developSidebar",previous:{title:"Deployment",permalink:"/docs/develop/deployment"},next:{title:"Backups",permalink:"/docs/develop/backups"}},d={},l=[{value:"Run the tests",id:"run-the-tests",level:2},{value:"Always monitor the iOS simulator!",id:"always-monitor-the-ios-simulator",level:2},{value:"About app_test.dart",id:"about-app_testdart",level:2},{value:"About test_outcome.dart",id:"about-test_outcomedart",level:2},{value:"About run_tests_single.sh and app_test_single.dart",id:"about-run_tests_singlesh-and-app_test_singledart",level:2},{value:"Coverage",id:"coverage",level:2},{value:"Run the simulator with test data",id:"run-the-simulator-with-test-data",level:2},{value:"Continuous integration",id:"continuous-integration",level:2},{value:"Test fixture design",id:"test-fixture-design",level:2},{value:"Fixture Paths",id:"fixture-paths",level:3},{value:"AssetCollectionBuilder",id:"assetcollectionbuilder",level:3},{value:"TestFixture singleton",id:"testfixture-singleton",level:3}];function h(e){const t={a:"a",admonition:"admonition",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",mdxAdmonitionTitle:"mdxAdmonitionTitle",p:"p",pre:"pre",ul:"ul",...(0,s.a)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(t.header,{children:(0,n.jsx)(t.h1,{id:"testing",children:"Testing"})}),"\n",(0,n.jsxs)(t.p,{children:["The current goal of testing in GeoGardenClub is to prevent ",(0,n.jsx)(t.em,{children:"catastrophic regression"}),". In other words, we want our tests to ensure that changes to the code do not result in an app where important features no longer work. This means that our test suite should ensure that:"]}),"\n",(0,n.jsxs)(t.ul,{children:["\n",(0,n.jsx)(t.li,{children:'All commonly accessed screens display without error. (The tests might not check screens that are displayed "rarely", such as those resulting from anomalous conditions like network instability.)'}),"\n",(0,n.jsx)(t.li,{children:"CRUD operations on entities can be performed successfully when available."}),"\n",(0,n.jsx)(t.li,{children:"Buttons on all commonly accessed screens, when tapped, do not generate an error, and the resulting screen is checked to see that at least some of the intended results are displayed."}),"\n"]}),"\n",(0,n.jsx)(t.h2,{id:"run-the-tests",children:"Run the tests"}),"\n",(0,n.jsxs)(t.p,{children:["To run the test suite, invoke ",(0,n.jsx)(t.code,{children:"./run_tests.sh"}),". It should produce output similar to the following:"]}),"\n",(0,n.jsx)(t.pre,{children:(0,n.jsx)(t.code,{className:"language-shell",children:"~/GitHub/geogardenclub/ggc_app git:[issue-235]\n./run_tests.sh\n+ flutter test integration_test/app_test.dart --coverage\n00:15 +0: loading /Users/philipjohnson/GitHub/geogardenclub/ggc_app/integration_test/app_test.dart Ru00:41 +0: loading /Users/philipjohnson/GitHub/geogardenclub/ggc_app/integration_test/app_test.dart \n00:48 +0: loading /Users/philipjohnson/GitHub/geogardenclub/ggc_app/integration_test/app_test.dart 6.6s\nXcode build done. 33.2s\n00:54 +0: GGC Integration Test (All) Fixture 1 Tests \nTesting admin feature\nTesting badge feature\nTesting chapter feature\nTesting chat feature\nTesting crop feature\nTesting garden feature\nTesting gardener feature\nTesting geobot feature\nTesting home feature\nTesting observation feature\nTesting outcome feature\nTesting planting feature\nTesting settings feature\nTesting task feature\nTesting variety feature\n02:00 +1: All tests passed! \n+ genhtml -q coverage/lcov.info -o coverage/html\nOverall coverage rate:\n source files: 472\n lines.......: 35.0% (4464 of 12740 lines)\n functions...: no data found\nMessage summary:\n no messages were reported\n"})}),"\n",(0,n.jsx)(t.p,{children:"Here are some important takeaways:"}),"\n",(0,n.jsxs)(t.ul,{children:["\n",(0,n.jsx)(t.li,{children:'We only write integration tests; no unit or widget tests. This is to maximize our "return on investment": we want to implement and maintain the least amount of testing code possible while still catching as many bugs that we introduce from writing new code as we can.'}),"\n",(0,n.jsxs)(t.li,{children:['Our tests run with a specific "test fixture" (currently we\'re using one called Test Fixture 1). This is a sample dataset containing test values for most or all of the entities in our system (i.e. chapters, beds, gardens, gardeners, etc.). This sample dataset is stored in ',(0,n.jsx)(t.code,{children:"assets/test/fixture1"}),". In the future, we might write tests that require a different fixture."]}),"\n",(0,n.jsx)(t.li,{children:"Our test architecture is organized around features."}),"\n",(0,n.jsx)(t.li,{children:"We compute coverage as a simple check on the quality of the test set. We do not strive for 100% coverage, but we can clearly do better than (say) 35%. In addition, the coverage report provides an efficient way to find important areas of the code base that have not yet been tested."}),"\n"]}),"\n",(0,n.jsx)(t.h2,{id:"always-monitor-the-ios-simulator",children:"Always monitor the iOS simulator!"}),"\n",(0,n.jsxs)(t.admonition,{type:"warning",children:[(0,n.jsx)(t.mdxAdmonitionTitle,{}),(0,n.jsx)(t.p,{children:"If testing with the iOS simulator, the testing process will occasionally (and unpredictably) pause waiting for you to click on a button to allow pasting:"}),(0,n.jsx)("img",{src:"/img/develop/testing/core-simulator-bridge.png"}),(0,n.jsx)(t.p,{children:"For this reason, it's important to always monitor the simulator at least until the tests start, because you might need to click a button to let the tests proceed. Otherwise the test process will hang indefinitely."}),(0,n.jsx)(t.p,{children:"This is a security feature in the iOS operating system. There is apparently no way to disable it at the current time."})]}),"\n",(0,n.jsx)(t.h2,{id:"about-app_testdart",children:"About app_test.dart"}),"\n",(0,n.jsxs)(t.p,{children:["To further understand the test process, it's helpful to review the code that is run by the ",(0,n.jsx)(t.code,{children:"./run_tests.sh"})," command:"]}),"\n",(0,n.jsx)(t.pre,{children:(0,n.jsx)(t.code,{className:"language-dart",children:"// integration_test/app_test.dart\nvoid main() {\n IntegrationTestWidgetsFlutterBinding.ensureInitialized();\n group('GGC Integration Test (All)', () {\n patrolWidgetTest('Fixture 1 Tests', (PatrolTester $) async {\n await Firebase.initializeApp();\n setFirebaseUiIsTestMode(true);\n FirebaseAuth mockAuth = MockFirebaseAuth();\n String email = 'jennacorindeane@gmail.com';\n mockAuth.createUserWithEmailAndPassword(email: email, password: '');\n TestFixture testFixture = await TestFixture.getInstance(testFixture1Path);\n await $.pumpWidgetAndSettle(ProviderScope(\n overrides: [\n firebaseAuthProvider.overrideWithValue(mockAuth),\n badgesProvider.overrideWith((_) => testFixture.getBadgesStream()),\n badgeDatabaseProvider.overrideWith((_) => testFixture.getBadgeDatabase()),\n badgeInstancesProvider.overrideWith((_) => testFixture.getBadgeInstancesStream()),\n badgeInstanceDatabaseProvider.overrideWith((_) => testFixture.getBadgeInstanceDatabase()),\n bedsProvider.overrideWith((_) => testFixture.getBedsStream()),\n bedDatabaseProvider.overrideWith((ref) => testFixture.getBedDatabase()),\n chaptersProvider.overrideWith((_) => testFixture.getChaptersStream()),\n chapterDatabaseProvider.overrideWith((_) => testFixture.getChapterDatabase()),\n chatRoomDatabaseProvider.overrideWith((_) => testFixture.getChatRoomDatabase()),\n chatUserDatabaseProvider.overrideWith((_) => testFixture.getChatUserDatabase()),\n cropsProvider.overrideWith((_) => testFixture.getCropsStream()),\n cropDatabaseProvider.overrideWith((_) => testFixture.getCropDatabase()),\n editorsProvider.overrideWith((_) => testFixture.getEditorsStream()),\n editorDatabaseProvider.overrideWith((_) => testFixture.getEditorDatabase()),\n familiesProvider.overrideWith((_) => testFixture.getFamiliesStream()),\n familyDatabaseProvider.overrideWith((_) => testFixture.getFamilyDatabase()),\n gardensProvider.overrideWith((_) => testFixture.getGardensStream()),\n gardenDatabaseProvider.overrideWith((_) => testFixture.getGardenDatabase()),\n gardenersProvider.overrideWith((_) => testFixture.getGardenersStream()),\n gardenerDatabaseProvider.overrideWith((_) => testFixture.getGardenerDatabase()),\n observationsProvider.overrideWith((_) => testFixture.getObservationsStream()),\n observationDatabaseProvider.overrideWith((_) => testFixture.getObservationDatabase()),\n outcomesProvider.overrideWith((_) => testFixture.getOutcomesStream()),\n outcomeDatabaseProvider.overrideWith((_) => testFixture.getOutcomeDatabase()),\n plantingsProvider.overrideWith((_) => testFixture.getPlantingsStream()),\n plantingDatabaseProvider.overrideWith((_) => testFixture.getPlantingDatabase()),\n rolesProvider.overrideWith((_) => testFixture.getRolesStream()),\n roleDatabaseProvider.overrideWith((_) => testFixture.getRoleDatabase()),\n tagsProvider.overrideWith((_) => testFixture.getTagsStream()),\n tagDatabaseProvider.overrideWith((_) => testFixture.getTagDatabase()),\n tasksProvider.overrideWith((_) => testFixture.getTasksStream()),\n taskDatabaseProvider.overrideWith((_) => testFixture.getTaskDatabase()),\n usersProvider.overrideWith((_) => testFixture.getUsersStream()),\n userDatabaseProvider.overrideWith((_) => testFixture.getUserDatabase()),\n varietiesProvider.overrideWith((_) => testFixture.getVarietiesStream()),\n varietyDatabaseProvider.overrideWith((_) => testFixture.getVarietyDatabase()),\n ],\n child: const MyApp(),\n ));\n expect($(HomeScreen).visible, equals(true), reason: 'Login fails');\n await checkIntegrity($, reason: 'startup');\n await testAdmin($);\n await checkIntegrity($, reason: 'admin feature');\n await testBadge($);\n await checkIntegrity($, reason: 'badge feature');\n await testChapter($);\n await checkIntegrity($, reason: 'chapter feature');\n await testChat($);\n await checkIntegrity($, reason: 'chat feature');\n await testCrop($);\n await checkIntegrity($, reason: 'crop feature');\n await testGarden($);\n await checkIntegrity($, reason: 'garden feature');\n await testGardener($);\n await checkIntegrity($, reason: 'gardener feature');\n await testGeoBot($);\n await checkIntegrity($, reason: 'geobot feature');\n await testHome($);\n await checkIntegrity($, reason: 'home feature');\n await testObservation($);\n await checkIntegrity($, reason: 'observation feature');\n await testOutcome($);\n await checkIntegrity($, reason: 'outcome feature');\n await testPlanting($);\n await checkIntegrity($, reason: 'planting feature');\n await testSettings($);\n await checkIntegrity($, reason: 'settings feature');\n await testTask($);\n await checkIntegrity($, reason: 'task feature');\n await testVariety($);\n await checkIntegrity($, reason: 'variety feature');\n });\n });\n}\n"})}),"\n",(0,n.jsx)(t.p,{children:"Here are the important takeaways:"}),"\n",(0,n.jsxs)(t.ul,{children:["\n",(0,n.jsxs)(t.li,{children:["We use the ",(0,n.jsx)(t.a,{href:"https://patrol.leancode.co/finders/overview",children:"Patrol Finders"})," package, which provides a very helpful syntactic sugar over the built-in Flutter testing package. We do not use the full Patrol package, just their Patrol Finder package."]}),"\n",(0,n.jsx)(t.li,{children:"We use the Riverpod overrides feature so that during testing, our code manipulates the test fixture data rather than the data in the Firebase database."}),"\n",(0,n.jsxs)(t.li,{children:["We simulate Firebase authentication and the app starts up with the user ",(0,n.jsx)(t.a,{href:"mailto:jennacorindeane@gmail.com",children:"jennacorindeane@gmail.com"}),' already logged in. So, we don\'t currently test the registration or signin workflows. The app "starts" by displaying the Home screen for Jenna.']}),"\n",(0,n.jsx)(t.li,{children:'We test each feature by calling a "test" function (i.e. testChapter, testCrop, etc.).'}),"\n",(0,n.jsx)(t.li,{children:"After testing each feature, the code runs the Check Integrity admin function to ensure that the test of the previous feature did not introduce a database inconsistency."}),"\n",(0,n.jsxs)(t.li,{children:["You should rarely need to edit this ",(0,n.jsx)(t.code,{children:"app_test.dart"}),' file. Instead, you will usually edit one of the "test" feature files (i.e. testChapter.dart, testCrop.dart, etc.) You will normally need to edit this file only when you want to introduce the testing of a new feature.']}),"\n"]}),"\n",(0,n.jsx)(t.h2,{id:"about-test_outcomedart",children:"About test_outcome.dart"}),"\n",(0,n.jsx)(t.p,{children:"Let's now look at one of the test functions for a specific feature:"}),"\n",(0,n.jsx)(t.pre,{children:(0,n.jsx)(t.code,{className:"language-dart",children:"// integration_test/features/outcome/test_outcome.dart\nFuture testOutcome(PatrolTester $) async {\n // ignore: avoid_print\n print('Testing outcome feature');\n await gotoDrawerScreen($, GardenIndexScreen, 'Gardens');\n expect($(GardenIndexScreen).visible, equals(true));\n await $('Details').tap();\n expect($(GardenDetailsScreen).visible, equals(true));\n await $(BottomNavigationBar).$('Outcomes').tap();\n expect($(GardenDetailsOutcomesView).visible, equals(true));\n await $(BackButton).tap();\n}\n"})}),"\n",(0,n.jsx)(t.p,{children:"Here are some important takeaways:"}),"\n",(0,n.jsxs)(t.ul,{children:["\n",(0,n.jsx)(t.li,{children:"Each test function starts by printing a line of output indicating that the test of this feature is starting. That makes it easier to see how far testing has gotten and helps pinpoint the location of problems when testing fails."}),"\n",(0,n.jsxs)(t.li,{children:["We use Patrol Finder syntax to locate widgets and manipulate them through searching for widgets of a particular type and/or containing a particular text string. ",(0,n.jsx)(t.em,{children:"Please avoid creating Keys for testing."})," Patrol Finders make it possible to test the source code without introducing new lines of code purely for the purpose of test support."]}),"\n",(0,n.jsx)(t.li,{children:"Currently, features are not tested very well at all, as this code shows. You will do most of your test development by editing these feature-level test files. You should feel free to refactor them and perhaps create new test files within the feature directory if useful."}),"\n",(0,n.jsxs)(t.li,{children:["The final line of code is ",(0,n.jsx)(t.code,{children:"await $(BackButton).tap();"}),". This is because each feature tests expects the system to be in a state where the Drawer menu icon is available to be tapped on. The feature tests do not require a specific screen to be displayed, any screen in which the Drawer menu icon is displayed is good enough."]}),"\n"]}),"\n",(0,n.jsx)(t.h2,{id:"about-run_tests_singlesh-and-app_test_singledart",children:"About run_tests_single.sh and app_test_single.dart"}),"\n",(0,n.jsx)(t.p,{children:"While developing the test for a feature, it is humbug and time-consuming to have to run the entire test suite each time you want to run your newly developed test code."}),"\n",(0,n.jsxs)(t.p,{children:["To speed up testing, you can use the command ",(0,n.jsx)(t.code,{children:"./run_tests_single.sh"}),". This runs the ",(0,n.jsx)(t.code,{children:"app_test_single.dart"})," file, which looks similar to this:"]}),"\n",(0,n.jsx)(t.pre,{children:(0,n.jsx)(t.code,{className:"language-dart",children:"// integration_test/app_test_single.dart\nvoid main() {\n IntegrationTestWidgetsFlutterBinding.ensureInitialized();\n group('GGC Integration Test (Single)', () {\n patrolWidgetTest('Fixture 1 Tests', (PatrolTester $) async {\n await Firebase.initializeApp();\n setFirebaseUiIsTestMode(true);\n FirebaseAuth mockAuth = MockFirebaseAuth();\n String email = 'jennacorindeane@gmail.com';\n mockAuth.createUserWithEmailAndPassword(email: email, password: '');\n TestFixture testFixture = await TestFixture.getInstance(testFixture1Path);\n await $.pumpWidgetAndSettle(ProviderScope(\n overrides: [\n firebaseAuthProvider.overrideWithValue(mockAuth),\n badgesProvider.overrideWith((_) => testFixture.getBadgesStream()),\n badgeDatabaseProvider.overrideWith((_) => testFixture.getBadgeDatabase()),\n :\n :\n varietiesProvider.overrideWith((_) => testFixture.getVarietiesStream()),\n varietyDatabaseProvider.overrideWith((_) => testFixture.getVarietyDatabase()),\n ],\n child: const MyApp(),\n ));\n expect($(HomeScreen).visible, equals(true), reason: 'Login fails');\n await testChat($);\n });\n });\n}\n"})}),"\n",(0,n.jsx)(t.p,{children:"Here are some important takeaways:"}),"\n",(0,n.jsxs)(t.ul,{children:["\n",(0,n.jsx)(t.li,{children:"You can freely edit this file in your branch to focus on the specific feature of interest."}),"\n",(0,n.jsxs)(t.li,{children:['Sometimes you might want to check multiple features at once, that\'s fine. You do you. The idea is that this is a kind of "sandbox" for you to develop tests so that you are not wishing to edit the global ',(0,n.jsx)(t.code,{children:"./run_tests.sh"})," and ",(0,n.jsx)(t.code,{children:"app_test.dart"})," files to speed up testing."]}),"\n"]}),"\n",(0,n.jsx)(t.h2,{id:"coverage",children:"Coverage"}),"\n",(0,n.jsxs)(t.p,{children:["It can sometimes be interesting to look at the coverage of testing. After running the test suite, you can open the file ",(0,n.jsx)(t.code,{children:"coverage/html/index.html"}),", which will look similar to this:"]}),"\n",(0,n.jsx)("img",{src:"/img/develop/testing/coverage.png"}),"\n",(0,n.jsx)(t.p,{children:"There are clickable links that you can use to drill down to see which statements have been executed and which have not been."}),"\n",(0,n.jsx)(t.p,{children:"Use coverage information wisely."}),"\n",(0,n.jsx)(t.h2,{id:"run-the-simulator-with-test-data",children:"Run the simulator with test data"}),"\n",(0,n.jsx)(t.p,{children:'During test design, it can be helpful to run the simulator with the test data loaded into it. That way you can "walk through" various interactions and see what the system will do before writing the test.'}),"\n",(0,n.jsxs)(t.p,{children:["To facilitate this, you can run ",(0,n.jsx)(t.code,{children:"lib/main_test_fixture.dart"})," using your IDE of choice. This file is very similar to ",(0,n.jsx)(t.code,{children:"lib/main.dart"}),", except that it does the necessary Riverpod overrides so that the system will load the test fixture data and will login to the user ",(0,n.jsx)(t.a,{href:"mailto:jennacorindeane@gmail.com",children:"jennacorindeane@gmail.com"}),"."]}),"\n",(0,n.jsx)(t.h2,{id:"continuous-integration",children:"Continuous integration"}),"\n",(0,n.jsx)(t.p,{children:"It would be sweet to run the integration tests each time there is a commit to main."}),"\n",(0,n.jsx)(t.p,{children:"I'm working on it."}),"\n",(0,n.jsx)(t.h2,{id:"test-fixture-design",children:"Test fixture design"}),"\n",(0,n.jsxs)(t.p,{children:["We use JSON files to create the test data for the tests. The test files are located in one of (potentially many) directories named ",(0,n.jsx)(t.code,{children:"assets\\test\\fixtureN"}),', when "N" is a number uniquely identifying the fixture. Currently, we only have one fixture directory.']}),"\n",(0,n.jsx)(t.p,{children:"Each fixture directory must contain the following files:"}),"\n",(0,n.jsxs)(t.ul,{children:["\n",(0,n.jsx)(t.li,{children:(0,n.jsx)(t.code,{children:"badgeData.json"})}),"\n",(0,n.jsx)(t.li,{children:(0,n.jsx)(t.code,{children:"badgeInstanceData.json"})}),"\n",(0,n.jsx)(t.li,{children:(0,n.jsx)(t.code,{children:"bedData.json"})}),"\n",(0,n.jsx)(t.li,{children:(0,n.jsx)(t.code,{children:"chapterData.json"})}),"\n",(0,n.jsx)(t.li,{children:(0,n.jsx)(t.code,{children:"cropData.json"})}),"\n",(0,n.jsx)(t.li,{children:(0,n.jsx)(t.code,{children:"editorData.json"})}),"\n",(0,n.jsx)(t.li,{children:(0,n.jsx)(t.code,{children:"familyData.json"})}),"\n",(0,n.jsx)(t.li,{children:(0,n.jsx)(t.code,{children:"gardenData.json"})}),"\n",(0,n.jsx)(t.li,{children:(0,n.jsx)(t.code,{children:"gardenerData.json"})}),"\n",(0,n.jsx)(t.li,{children:(0,n.jsx)(t.code,{children:"observationData.json"})}),"\n",(0,n.jsx)(t.li,{children:(0,n.jsx)(t.code,{children:"outcomeData.json"})}),"\n",(0,n.jsx)(t.li,{children:(0,n.jsx)(t.code,{children:"plantingData.json"})}),"\n",(0,n.jsx)(t.li,{children:(0,n.jsx)(t.code,{children:"roleData.json"})}),"\n",(0,n.jsx)(t.li,{children:(0,n.jsx)(t.code,{children:"tagData.json"})}),"\n",(0,n.jsx)(t.li,{children:(0,n.jsx)(t.code,{children:"taskData.json"})}),"\n",(0,n.jsx)(t.li,{children:(0,n.jsx)(t.code,{children:"userData.json"})}),"\n",(0,n.jsx)(t.li,{children:(0,n.jsx)(t.code,{children:"varietyData.json"})}),"\n"]}),"\n",(0,n.jsxs)(t.admonition,{type:"info",children:[(0,n.jsx)(t.p,{children:"The JSON files need to have integrity, so their ids must align. Since many of the GGC IDs end with a four digit millis field we've assigned each type a unique millis field."}),(0,n.jsxs)(t.ul,{children:["\n",(0,n.jsx)(t.li,{children:"bedIDs end with 3456."}),"\n",(0,n.jsx)(t.li,{children:"cropIDs end with 5678."}),"\n",(0,n.jsx)(t.li,{children:"gardenIDs end with 7890."}),"\n",(0,n.jsx)(t.li,{children:"observationIDs end with 4567."}),"\n",(0,n.jsx)(t.li,{children:"outcomeIDs end with 2345."}),"\n",(0,n.jsx)(t.li,{children:"plantingIDs end with 1234."}),"\n",(0,n.jsx)(t.li,{children:"seedIDs end with 6789."}),"\n",(0,n.jsx)(t.li,{children:"taskIDs end with 8901."}),"\n",(0,n.jsx)(t.li,{children:"varietyIDs end with 9012."}),"\n",(0,n.jsx)(t.li,{children:"badgeInstances end with 9876."}),"\n"]})]}),"\n",(0,n.jsx)(t.h3,{id:"fixture-paths",children:"Fixture Paths"}),"\n",(0,n.jsxs)(t.p,{children:["The ",(0,n.jsx)(t.code,{children:"lib/features/fixture_paths.dart"})," file defines two constants:"]}),"\n",(0,n.jsxs)(t.ul,{children:["\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.code,{children:"testFixturePath"})," - the path to the test fixture directory. This constant is used to load the test data in the tests."]}),"\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.code,{children:"monarchFixturePath"})," - the path to the Monarch fixture directory used by ",(0,n.jsx)(t.code,{children:"WithMonarchData"}),"."]}),"\n"]}),"\n",(0,n.jsx)(t.h3,{id:"assetcollectionbuilder",children:"AssetCollectionBuilder"}),"\n",(0,n.jsxs)(t.p,{children:["To facilitate the loading of the fixture files, we have created the ",(0,n.jsx)(t.code,{children:"AssetCollectionBuilder"})," class. This class has three static methods to produce each of the collections from a fixture path. The three methods are as follows:"]}),"\n",(0,n.jsxs)(t.ul,{children:["\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.code,{children:"Future> getTypes(String assetPath)"})," - loads the data from the fixture file and returns a list of the type."]}),"\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.code,{children:"Future>> getTypesStream(String assetPath)"})," - loads the data from the fixture file and returns a stream of a list of the type."]}),"\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.code,{children:"Future getTypeCollection(String assetPath)"})," - loads the data from the fixture file and returns a collection of the type."]}),"\n"]}),"\n",(0,n.jsxs)(t.p,{children:["For example, to create a ",(0,n.jsx)(t.code,{children:"BedCollection"})," from the fixture path, use the following code:"]}),"\n",(0,n.jsx)(t.pre,{children:(0,n.jsx)(t.code,{children:"final bedCollection = await AssetCollectionBuilder.getBedCollection(testFixturePath);\n"})}),"\n",(0,n.jsxs)(t.p,{children:["In addition, the ",(0,n.jsx)(t.code,{children:"AssetCollectionBuilder"})," class has three build methods that build the collections with all the data like the ",(0,n.jsx)(t.code,{children:"WithAllData"})," classes. The methods are as follows"]}),"\n",(0,n.jsxs)(t.ul,{children:["\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.code,{children:"buildChapterCollection(String assetPath, String chapterId)"})," - builds a ",(0,n.jsx)(t.code,{children:"ChapterCollection"}),"."]}),"\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.code,{children:"buildGardenCollection(String assetPath, String gardenId)"})," - builds a ",(0,n.jsx)(t.code,{children:"GardenCollection"}),"."]}),"\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.code,{children:"buildUserCollection(String assetPath, String currentUserID, String currentUserUID)"})," - builds a ",(0,n.jsx)(t.code,{children:"UserCollection"}),"."]}),"\n"]}),"\n",(0,n.jsx)(t.h3,{id:"testfixture-singleton",children:"TestFixture singleton"}),"\n",(0,n.jsxs)(t.p,{children:["The ",(0,n.jsx)(t.code,{children:"TestFixture"})," singleton is used to load the test fixture data. The singleton has the following methods:"]}),"\n",(0,n.jsxs)(t.ul,{children:["\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.code,{children:"getInstance(String assetPath)"})," - returns a Future with the singleton instance. The first time it is called, it will load the test fixture data."]}),"\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.code,{children:"setup()"})," - initializes the singleton by loading the test fixture data."]}),"\n"]}),"\n",(0,n.jsx)(t.p,{children:"There are two methods for each entity in the test fixture:"}),"\n",(0,n.jsxs)(t.ul,{children:["\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.code,{children:"getStream()"})," - returns a Stream of the List of the entities from the test fixture."]}),"\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.code,{children:"getDatabase()"})," - returns The ",(0,n.jsx)(t.code,{children:"FixtureDatabase"})," from the test fixture."]}),"\n"]})]})}function c(e={}){const{wrapper:t}={...(0,s.a)(),...e.components};return t?(0,n.jsx)(t,{...e,children:(0,n.jsx)(h,{...e})}):h(e)}},1151:(e,t,i)=>{i.d(t,{Z:()=>o,a:()=>a});var n=i(7294);const s={},r=n.createContext(s);function a(e){const t=n.useContext(r);return n.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function o(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:a(e.components),n.createElement(r.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/1e5c498d.da539c3e.js b/assets/js/1e5c498d.da539c3e.js deleted file mode 100644 index 1b8d6d21..00000000 --- a/assets/js/1e5c498d.da539c3e.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkgeogardenclub_github_io=self.webpackChunkgeogardenclub_github_io||[]).push([[1217],{798:(e,t,i)=>{i.r(t),i.d(t,{assets:()=>d,contentTitle:()=>r,default:()=>c,frontMatter:()=>a,metadata:()=>o,toc:()=>l});var n=i(5893),s=i(1151);const a={hide_table_of_contents:!1},r="Testing",o={id:"develop/testing",title:"Testing",description:"This page documents our current approach to testing the GeoGardenClub app.",source:"@site/docs/develop/testing.md",sourceDirName:"develop",slug:"/develop/testing",permalink:"/docs/develop/testing",draft:!1,unlisted:!1,tags:[],version:"current",frontMatter:{hide_table_of_contents:!1},sidebar:"developSidebar",previous:{title:"Deployment",permalink:"/docs/develop/deployment"},next:{title:"Backups",permalink:"/docs/develop/backups"}},d={},l=[{value:"Run the tests",id:"run-the-tests",level:2},{value:"Always watch the iOS simulator!",id:"always-watch-the-ios-simulator",level:2},{value:"About app_test.dart",id:"about-app_testdart",level:2},{value:"About test_outcome.dart",id:"about-test_outcomedart",level:2},{value:"About run_tests_single.sh and app_test_single.dart",id:"about-run_tests_singlesh-and-app_test_singledart",level:2},{value:"Coverage",id:"coverage",level:2},{value:"Run the simulator with test data",id:"run-the-simulator-with-test-data",level:2},{value:"Continuous integration",id:"continuous-integration",level:2},{value:"Test fixture design",id:"test-fixture-design",level:2},{value:"Fixture Paths",id:"fixture-paths",level:3},{value:"AssetCollectionBuilder",id:"assetcollectionbuilder",level:3},{value:"TestFixture singleton",id:"testfixture-singleton",level:3}];function h(e){const t={a:"a",admonition:"admonition",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",mdxAdmonitionTitle:"mdxAdmonitionTitle",p:"p",pre:"pre",ul:"ul",...(0,s.a)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(t.header,{children:(0,n.jsx)(t.h1,{id:"testing",children:"Testing"})}),"\n",(0,n.jsx)(t.p,{children:"This page documents our current approach to testing the GeoGardenClub app."}),"\n",(0,n.jsx)(t.h2,{id:"run-the-tests",children:"Run the tests"}),"\n",(0,n.jsxs)(t.p,{children:["To run the test suite, invoke ",(0,n.jsx)(t.code,{children:"./run_tests.sh"}),". It should produce output similar to the following:"]}),"\n",(0,n.jsx)(t.pre,{children:(0,n.jsx)(t.code,{className:"language-shell",children:"~/GitHub/geogardenclub/ggc_app git:[issue-235]\n./run_tests.sh\n+ flutter test integration_test/app_test.dart --coverage\n00:15 +0: loading /Users/philipjohnson/GitHub/geogardenclub/ggc_app/integration_test/app_test.dart Ru00:41 +0: loading /Users/philipjohnson/GitHub/geogardenclub/ggc_app/integration_test/app_test.dart \n00:48 +0: loading /Users/philipjohnson/GitHub/geogardenclub/ggc_app/integration_test/app_test.dart 6.6s\nXcode build done. 33.2s\n00:54 +0: GGC Integration Test (All) Fixture 1 Tests \nTesting admin feature\nTesting badge feature\nTesting chapter feature\nTesting chat feature\nTesting crop feature\nTesting garden feature\nTesting gardener feature\nTesting geobot feature\nTesting home feature\nTesting observation feature\nTesting outcome feature\nTesting planting feature\nTesting settings feature\nTesting task feature\nTesting variety feature\n02:00 +1: All tests passed! \n+ genhtml -q coverage/lcov.info -o coverage/html\nOverall coverage rate:\n source files: 472\n lines.......: 35.0% (4464 of 12740 lines)\n functions...: no data found\nMessage summary:\n no messages were reported\n"})}),"\n",(0,n.jsx)(t.p,{children:"Here are some important takeaways:"}),"\n",(0,n.jsxs)(t.ul,{children:["\n",(0,n.jsx)(t.li,{children:'We only write integration tests; no unit or widget tests. This is to maximize our "return on investment": we want to implement and maintain the least amount of testing code possible while still catching as many bugs that we introduce from writing new code as we can.'}),"\n",(0,n.jsxs)(t.li,{children:['Our tests run with a specific "test fixture" (currently we\'re using one called Test Fixture 1). This is a sample dataset containing test values for most or all of the entities in our system (i.e. chapters, beds, gardens, gardeners, etc.). This sample dataset is stored in ',(0,n.jsx)(t.code,{children:"assets/test/fixture1"}),". In the future, we might write tests that require a different fixture."]}),"\n",(0,n.jsx)(t.li,{children:"Our test architecture is organized around features."}),"\n",(0,n.jsx)(t.li,{children:"We compute coverage as a simple check on the quality of the test set. We do not strive for 100% coverage, but we can clearly do better than (say) 35%. In addition, the coverage report provides an efficient way to find important areas of the code base that have not yet been tested."}),"\n"]}),"\n",(0,n.jsx)(t.h2,{id:"always-watch-the-ios-simulator",children:"Always watch the iOS simulator!"}),"\n",(0,n.jsxs)(t.admonition,{type:"warning",children:[(0,n.jsx)(t.mdxAdmonitionTitle,{}),(0,n.jsx)(t.p,{children:"If testing with the iOS simulator, the testing process will occasionally (and unpredictably) pause waiting for you to click on a button to allow pasting:"}),(0,n.jsx)("img",{src:"/img/develop/testing/core-simulator-bridge.png"}),(0,n.jsx)(t.p,{children:"For this reason, it's important to always watch the simulator at least until the tests start, because you might need to click a button to let the tests proceed."}),(0,n.jsx)(t.p,{children:"This is a security feature in the iOS operating system. I do not know of a way to disable it."})]}),"\n",(0,n.jsx)(t.h2,{id:"about-app_testdart",children:"About app_test.dart"}),"\n",(0,n.jsxs)(t.p,{children:["To further understand the test process, it's helpful to review the code that is run by the ",(0,n.jsx)(t.code,{children:"./run_tests.sh"})," command:"]}),"\n",(0,n.jsx)(t.pre,{children:(0,n.jsx)(t.code,{className:"language-dart",children:"// integration_test/app_test.dart\nvoid main() {\n IntegrationTestWidgetsFlutterBinding.ensureInitialized();\n group('GGC Integration Test (All)', () {\n patrolWidgetTest('Fixture 1 Tests', (PatrolTester $) async {\n await Firebase.initializeApp();\n setFirebaseUiIsTestMode(true);\n FirebaseAuth mockAuth = MockFirebaseAuth();\n String email = 'jennacorindeane@gmail.com';\n mockAuth.createUserWithEmailAndPassword(email: email, password: '');\n TestFixture testFixture = await TestFixture.getInstance(testFixture1Path);\n await $.pumpWidgetAndSettle(ProviderScope(\n overrides: [\n firebaseAuthProvider.overrideWithValue(mockAuth),\n badgesProvider.overrideWith((_) => testFixture.getBadgesStream()),\n badgeDatabaseProvider.overrideWith((_) => testFixture.getBadgeDatabase()),\n badgeInstancesProvider.overrideWith((_) => testFixture.getBadgeInstancesStream()),\n badgeInstanceDatabaseProvider.overrideWith((_) => testFixture.getBadgeInstanceDatabase()),\n bedsProvider.overrideWith((_) => testFixture.getBedsStream()),\n bedDatabaseProvider.overrideWith((ref) => testFixture.getBedDatabase()),\n chaptersProvider.overrideWith((_) => testFixture.getChaptersStream()),\n chapterDatabaseProvider.overrideWith((_) => testFixture.getChapterDatabase()),\n chatRoomDatabaseProvider.overrideWith((_) => testFixture.getChatRoomDatabase()),\n chatUserDatabaseProvider.overrideWith((_) => testFixture.getChatUserDatabase()),\n cropsProvider.overrideWith((_) => testFixture.getCropsStream()),\n cropDatabaseProvider.overrideWith((_) => testFixture.getCropDatabase()),\n editorsProvider.overrideWith((_) => testFixture.getEditorsStream()),\n editorDatabaseProvider.overrideWith((_) => testFixture.getEditorDatabase()),\n familiesProvider.overrideWith((_) => testFixture.getFamiliesStream()),\n familyDatabaseProvider.overrideWith((_) => testFixture.getFamilyDatabase()),\n gardensProvider.overrideWith((_) => testFixture.getGardensStream()),\n gardenDatabaseProvider.overrideWith((_) => testFixture.getGardenDatabase()),\n gardenersProvider.overrideWith((_) => testFixture.getGardenersStream()),\n gardenerDatabaseProvider.overrideWith((_) => testFixture.getGardenerDatabase()),\n observationsProvider.overrideWith((_) => testFixture.getObservationsStream()),\n observationDatabaseProvider.overrideWith((_) => testFixture.getObservationDatabase()),\n outcomesProvider.overrideWith((_) => testFixture.getOutcomesStream()),\n outcomeDatabaseProvider.overrideWith((_) => testFixture.getOutcomeDatabase()),\n plantingsProvider.overrideWith((_) => testFixture.getPlantingsStream()),\n plantingDatabaseProvider.overrideWith((_) => testFixture.getPlantingDatabase()),\n rolesProvider.overrideWith((_) => testFixture.getRolesStream()),\n roleDatabaseProvider.overrideWith((_) => testFixture.getRoleDatabase()),\n tagsProvider.overrideWith((_) => testFixture.getTagsStream()),\n tagDatabaseProvider.overrideWith((_) => testFixture.getTagDatabase()),\n tasksProvider.overrideWith((_) => testFixture.getTasksStream()),\n taskDatabaseProvider.overrideWith((_) => testFixture.getTaskDatabase()),\n usersProvider.overrideWith((_) => testFixture.getUsersStream()),\n userDatabaseProvider.overrideWith((_) => testFixture.getUserDatabase()),\n varietiesProvider.overrideWith((_) => testFixture.getVarietiesStream()),\n varietyDatabaseProvider.overrideWith((_) => testFixture.getVarietyDatabase()),\n ],\n child: const MyApp(),\n ));\n expect($(HomeScreen).visible, equals(true), reason: 'Login fails');\n await checkIntegrity($, reason: 'startup');\n await testAdmin($);\n await checkIntegrity($, reason: 'admin feature');\n await testBadge($);\n await checkIntegrity($, reason: 'badge feature');\n await testChapter($);\n await checkIntegrity($, reason: 'chapter feature');\n await testChat($);\n await checkIntegrity($, reason: 'chat feature');\n await testCrop($);\n await checkIntegrity($, reason: 'crop feature');\n await testGarden($);\n await checkIntegrity($, reason: 'garden feature');\n await testGardener($);\n await checkIntegrity($, reason: 'gardener feature');\n await testGeoBot($);\n await checkIntegrity($, reason: 'geobot feature');\n await testHome($);\n await checkIntegrity($, reason: 'home feature');\n await testObservation($);\n await checkIntegrity($, reason: 'observation feature');\n await testOutcome($);\n await checkIntegrity($, reason: 'outcome feature');\n await testPlanting($);\n await checkIntegrity($, reason: 'planting feature');\n await testSettings($);\n await checkIntegrity($, reason: 'settings feature');\n await testTask($);\n await checkIntegrity($, reason: 'task feature');\n await testVariety($);\n await checkIntegrity($, reason: 'variety feature');\n });\n });\n}\n"})}),"\n",(0,n.jsx)(t.p,{children:"Here are the important takeaways:"}),"\n",(0,n.jsxs)(t.ul,{children:["\n",(0,n.jsxs)(t.li,{children:["We use the ",(0,n.jsx)(t.a,{href:"https://patrol.leancode.co/finders/overview",children:"Patrol Finders"})," package, which provides a very helpful syntactic sugar over the built-in Flutter testing package. We do not use the full Patrol package, just their Patrol Finder package."]}),"\n",(0,n.jsx)(t.li,{children:"We use the Riverpod overrides feature so that during testing, our code manipulates the test fixture data rather than the data in the Firebase database."}),"\n",(0,n.jsxs)(t.li,{children:["We simulate Firebase authentication and the app starts up with the user ",(0,n.jsx)(t.a,{href:"mailto:jennacorindeane@gmail.com",children:"jennacorindeane@gmail.com"}),' already logged in. So, we don\'t currently test the registration or signin workflows. The app "starts" by displaying the Home screen for Jenna.']}),"\n",(0,n.jsx)(t.li,{children:'We test each feature by calling a "test" function (i.e. testChapter, testCrop, etc.).'}),"\n",(0,n.jsx)(t.li,{children:"After testing each feature, the code runs the Check Integrity admin function to ensure that the test of the previous feature did not introduce a database inconsistency."}),"\n",(0,n.jsxs)(t.li,{children:["You should rarely need to edit this ",(0,n.jsx)(t.code,{children:"app_test.dart"}),' file. Instead, you will usually edit one of the "test" feature files (i.e. testChapter.dart, testCrop.dart, etc.) You will normally need to edit this file only when you want to introduce the testing of a new feature.']}),"\n"]}),"\n",(0,n.jsx)(t.h2,{id:"about-test_outcomedart",children:"About test_outcome.dart"}),"\n",(0,n.jsx)(t.p,{children:"Let's now look at one of the test functions for a specific feature:"}),"\n",(0,n.jsx)(t.pre,{children:(0,n.jsx)(t.code,{className:"language-dart",children:"// integration_test/features/outcome/test_outcome.dart\nFuture testOutcome(PatrolTester $) async {\n // ignore: avoid_print\n print('Testing outcome feature');\n await gotoDrawerScreen($, GardenIndexScreen, 'Gardens');\n expect($(GardenIndexScreen).visible, equals(true));\n await $('Details').tap();\n expect($(GardenDetailsScreen).visible, equals(true));\n await $(BottomNavigationBar).$('Outcomes').tap();\n expect($(GardenDetailsOutcomesView).visible, equals(true));\n await $(BackButton).tap();\n}\n"})}),"\n",(0,n.jsx)(t.p,{children:"Here are some important takeaways:"}),"\n",(0,n.jsxs)(t.ul,{children:["\n",(0,n.jsx)(t.li,{children:"Each test function starts by printing a line of output indicating that the test of this feature is starting. That makes it easier to see how far testing has gotten and helps pinpoint the location of problems when testing fails."}),"\n",(0,n.jsxs)(t.li,{children:["We use Patrol Finder syntax to locate widgets and manipulate them through searching for widgets of a particular type and/or containing a particular text string. ",(0,n.jsx)(t.em,{children:"Please avoid creating Keys for testing."})," Patrol Finders make it possible to test the source code without introducing new lines of code purely for the purpose of test support."]}),"\n",(0,n.jsx)(t.li,{children:"Currently, features are not tested very well at all, as this code shows. You will do most of your test development by editing these feature-level test files. You should feel free to refactor them and perhaps create new test files within the feature directory if useful."}),"\n",(0,n.jsxs)(t.li,{children:["The final line of code is ",(0,n.jsx)(t.code,{children:"await $(BackButton).tap();"}),". This is because each feature tests expects the system to be in a state where the Drawer menu icon is available to be tapped on. The feature tests do not require a specific screen to be displayed, any screen in which the Drawer menu icon is displayed is good enough."]}),"\n"]}),"\n",(0,n.jsx)(t.h2,{id:"about-run_tests_singlesh-and-app_test_singledart",children:"About run_tests_single.sh and app_test_single.dart"}),"\n",(0,n.jsx)(t.p,{children:"While developing the test for a feature, it is humbug and time-consuming to have to run the entire test suite each time you want to run your newly developed test code."}),"\n",(0,n.jsxs)(t.p,{children:["To speed up testing, you can use the command ",(0,n.jsx)(t.code,{children:"./run_tests_single.sh"}),". This runs the ",(0,n.jsx)(t.code,{children:"app_test_single.dart"})," file, which looks similar to this:"]}),"\n",(0,n.jsx)(t.pre,{children:(0,n.jsx)(t.code,{className:"language-dart",children:"// integration_test/app_test_single.dart\nvoid main() {\n IntegrationTestWidgetsFlutterBinding.ensureInitialized();\n group('GGC Integration Test (Single)', () {\n patrolWidgetTest('Fixture 1 Tests', (PatrolTester $) async {\n await Firebase.initializeApp();\n setFirebaseUiIsTestMode(true);\n FirebaseAuth mockAuth = MockFirebaseAuth();\n String email = 'jennacorindeane@gmail.com';\n mockAuth.createUserWithEmailAndPassword(email: email, password: '');\n TestFixture testFixture = await TestFixture.getInstance(testFixture1Path);\n await $.pumpWidgetAndSettle(ProviderScope(\n overrides: [\n firebaseAuthProvider.overrideWithValue(mockAuth),\n badgesProvider.overrideWith((_) => testFixture.getBadgesStream()),\n badgeDatabaseProvider.overrideWith((_) => testFixture.getBadgeDatabase()),\n :\n :\n varietiesProvider.overrideWith((_) => testFixture.getVarietiesStream()),\n varietyDatabaseProvider.overrideWith((_) => testFixture.getVarietyDatabase()),\n ],\n child: const MyApp(),\n ));\n expect($(HomeScreen).visible, equals(true), reason: 'Login fails');\n await testChat($);\n });\n });\n}\n"})}),"\n",(0,n.jsx)(t.p,{children:"Here are some important takeaways:"}),"\n",(0,n.jsxs)(t.ul,{children:["\n",(0,n.jsx)(t.li,{children:"You can freely edit this file in your branch to focus on the specific feature of interest."}),"\n",(0,n.jsxs)(t.li,{children:['Sometimes you might want to check multiple features at once, that\'s fine. You do you. The idea is that this is a kind of "sandbox" for you to develop tests so that you are not wishing to edit the global ',(0,n.jsx)(t.code,{children:"./run_tests.sh"})," and ",(0,n.jsx)(t.code,{children:"app_test.dart"})," files to speed up testing."]}),"\n"]}),"\n",(0,n.jsx)(t.h2,{id:"coverage",children:"Coverage"}),"\n",(0,n.jsxs)(t.p,{children:["It can sometimes be interesting to look at the coverage of testing. After running the test suite, you can open the file ",(0,n.jsx)(t.code,{children:"coverage/html/index.html"}),", which will look similar to this:"]}),"\n",(0,n.jsx)("img",{src:"/img/develop/testing/coverage.png"}),"\n",(0,n.jsx)(t.p,{children:"There are clickable links that you can use to drill down to see which statements have been executed and which have not been."}),"\n",(0,n.jsx)(t.p,{children:"Use coverage information wisely."}),"\n",(0,n.jsx)(t.h2,{id:"run-the-simulator-with-test-data",children:"Run the simulator with test data"}),"\n",(0,n.jsx)(t.p,{children:'During test design, it can be helpful to run the simulator with the test data loaded into it. That way you can "walk through" various interactions and see what the system will do before writing the test.'}),"\n",(0,n.jsxs)(t.p,{children:["To facilitate this, you can run ",(0,n.jsx)(t.code,{children:"lib/main_test_fixture.dart"})," using your IDE of choice. This file is very similar to ",(0,n.jsx)(t.code,{children:"lib/main.dart"}),", except that it does the necessary Riverpod overrides so that the system will load the test fixture data and will login to the user ",(0,n.jsx)(t.a,{href:"mailto:jennacorindeane@gmail.com",children:"jennacorindeane@gmail.com"}),"."]}),"\n",(0,n.jsx)(t.h2,{id:"continuous-integration",children:"Continuous integration"}),"\n",(0,n.jsx)(t.p,{children:"It would be sweet to run the integration tests each time there is a commit to main."}),"\n",(0,n.jsx)(t.p,{children:"I'm working on it."}),"\n",(0,n.jsx)(t.h2,{id:"test-fixture-design",children:"Test fixture design"}),"\n",(0,n.jsxs)(t.p,{children:["We use JSON files to create the test data for the tests. The test files are located in one of (potentially many) directories named ",(0,n.jsx)(t.code,{children:"assets\\test\\fixtureN"}),', when "N" is a number uniquely identifying the fixture. Currently, we only have one fixture directory.']}),"\n",(0,n.jsx)(t.p,{children:"Each fixture directory must contain the following files:"}),"\n",(0,n.jsxs)(t.ul,{children:["\n",(0,n.jsx)(t.li,{children:(0,n.jsx)(t.code,{children:"badgeData.json"})}),"\n",(0,n.jsx)(t.li,{children:(0,n.jsx)(t.code,{children:"badgeInstanceData.json"})}),"\n",(0,n.jsx)(t.li,{children:(0,n.jsx)(t.code,{children:"bedData.json"})}),"\n",(0,n.jsx)(t.li,{children:(0,n.jsx)(t.code,{children:"chapterData.json"})}),"\n",(0,n.jsx)(t.li,{children:(0,n.jsx)(t.code,{children:"cropData.json"})}),"\n",(0,n.jsx)(t.li,{children:(0,n.jsx)(t.code,{children:"editorData.json"})}),"\n",(0,n.jsx)(t.li,{children:(0,n.jsx)(t.code,{children:"familyData.json"})}),"\n",(0,n.jsx)(t.li,{children:(0,n.jsx)(t.code,{children:"gardenData.json"})}),"\n",(0,n.jsx)(t.li,{children:(0,n.jsx)(t.code,{children:"gardenerData.json"})}),"\n",(0,n.jsx)(t.li,{children:(0,n.jsx)(t.code,{children:"observationData.json"})}),"\n",(0,n.jsx)(t.li,{children:(0,n.jsx)(t.code,{children:"outcomeData.json"})}),"\n",(0,n.jsx)(t.li,{children:(0,n.jsx)(t.code,{children:"plantingData.json"})}),"\n",(0,n.jsx)(t.li,{children:(0,n.jsx)(t.code,{children:"roleData.json"})}),"\n",(0,n.jsx)(t.li,{children:(0,n.jsx)(t.code,{children:"tagData.json"})}),"\n",(0,n.jsx)(t.li,{children:(0,n.jsx)(t.code,{children:"taskData.json"})}),"\n",(0,n.jsx)(t.li,{children:(0,n.jsx)(t.code,{children:"userData.json"})}),"\n",(0,n.jsx)(t.li,{children:(0,n.jsx)(t.code,{children:"varietyData.json"})}),"\n"]}),"\n",(0,n.jsxs)(t.admonition,{type:"info",children:[(0,n.jsx)(t.p,{children:"The JSON files need to have integrity, so their ids must align. Since many of the GGC IDs end with a four digit millis field we've assigned each type a unique millis field."}),(0,n.jsxs)(t.ul,{children:["\n",(0,n.jsx)(t.li,{children:"bedIDs end with 3456."}),"\n",(0,n.jsx)(t.li,{children:"cropIDs end with 5678."}),"\n",(0,n.jsx)(t.li,{children:"gardenIDs end with 7890."}),"\n",(0,n.jsx)(t.li,{children:"observationIDs end with 4567."}),"\n",(0,n.jsx)(t.li,{children:"outcomeIDs end with 2345."}),"\n",(0,n.jsx)(t.li,{children:"plantingIDs end with 1234."}),"\n",(0,n.jsx)(t.li,{children:"seedIDs end with 6789."}),"\n",(0,n.jsx)(t.li,{children:"taskIDs end with 8901."}),"\n",(0,n.jsx)(t.li,{children:"varietyIDs end with 9012."}),"\n",(0,n.jsx)(t.li,{children:"badgeInstances end with 9876."}),"\n"]})]}),"\n",(0,n.jsx)(t.h3,{id:"fixture-paths",children:"Fixture Paths"}),"\n",(0,n.jsxs)(t.p,{children:["The ",(0,n.jsx)(t.code,{children:"lib/features/fixture_paths.dart"})," file defines two constants:"]}),"\n",(0,n.jsxs)(t.ul,{children:["\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.code,{children:"testFixturePath"})," - the path to the test fixture directory. This constant is used to load the test data in the tests."]}),"\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.code,{children:"monarchFixturePath"})," - the path to the Monarch fixture directory used by ",(0,n.jsx)(t.code,{children:"WithMonarchData"}),"."]}),"\n"]}),"\n",(0,n.jsx)(t.h3,{id:"assetcollectionbuilder",children:"AssetCollectionBuilder"}),"\n",(0,n.jsxs)(t.p,{children:["To facilitate the loading of the fixture files, we have created the ",(0,n.jsx)(t.code,{children:"AssetCollectionBuilder"})," class. This class has three static methods to produce each of the collections from a fixture path. The three methods are as follows:"]}),"\n",(0,n.jsxs)(t.ul,{children:["\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.code,{children:"Future> getTypes(String assetPath)"})," - loads the data from the fixture file and returns a list of the type."]}),"\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.code,{children:"Future>> getTypesStream(String assetPath)"})," - loads the data from the fixture file and returns a stream of a list of the type."]}),"\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.code,{children:"Future getTypeCollection(String assetPath)"})," - loads the data from the fixture file and returns a collection of the type."]}),"\n"]}),"\n",(0,n.jsxs)(t.p,{children:["For example, to create a ",(0,n.jsx)(t.code,{children:"BedCollection"})," from the fixture path, use the following code:"]}),"\n",(0,n.jsx)(t.pre,{children:(0,n.jsx)(t.code,{children:"final bedCollection = await AssetCollectionBuilder.getBedCollection(testFixturePath);\n"})}),"\n",(0,n.jsxs)(t.p,{children:["In addition, the ",(0,n.jsx)(t.code,{children:"AssetCollectionBuilder"})," class has three build methods that build the collections with all the data like the ",(0,n.jsx)(t.code,{children:"WithAllData"})," classes. The methods are as follows"]}),"\n",(0,n.jsxs)(t.ul,{children:["\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.code,{children:"buildChapterCollection(String assetPath, String chapterId)"})," - builds a ",(0,n.jsx)(t.code,{children:"ChapterCollection"}),"."]}),"\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.code,{children:"buildGardenCollection(String assetPath, String gardenId)"})," - builds a ",(0,n.jsx)(t.code,{children:"GardenCollection"}),"."]}),"\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.code,{children:"buildUserCollection(String assetPath, String currentUserID, String currentUserUID)"})," - builds a ",(0,n.jsx)(t.code,{children:"UserCollection"}),"."]}),"\n"]}),"\n",(0,n.jsx)(t.h3,{id:"testfixture-singleton",children:"TestFixture singleton"}),"\n",(0,n.jsxs)(t.p,{children:["The ",(0,n.jsx)(t.code,{children:"TestFixture"})," singleton is used to load the test fixture data. The singleton has the following methods:"]}),"\n",(0,n.jsxs)(t.ul,{children:["\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.code,{children:"getInstance(String assetPath)"})," - returns a Future with the singleton instance. The first time it is called, it will load the test fixture data."]}),"\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.code,{children:"setup()"})," - initializes the singleton by loading the test fixture data."]}),"\n"]}),"\n",(0,n.jsx)(t.p,{children:"There are two methods for each entity in the test fixture:"}),"\n",(0,n.jsxs)(t.ul,{children:["\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.code,{children:"getStream()"})," - returns a Stream of the List of the entities from the test fixture."]}),"\n",(0,n.jsxs)(t.li,{children:[(0,n.jsx)(t.code,{children:"getDatabase()"})," - returns The ",(0,n.jsx)(t.code,{children:"FixtureDatabase"})," from the test fixture."]}),"\n"]})]})}function c(e={}){const{wrapper:t}={...(0,s.a)(),...e.components};return t?(0,n.jsx)(t,{...e,children:(0,n.jsx)(h,{...e})}):h(e)}},1151:(e,t,i)=>{i.d(t,{Z:()=>o,a:()=>r});var n=i(7294);const s={},a=n.createContext(s);function r(e){const t=n.useContext(a);return n.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function o(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:r(e.components),n.createElement(a.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/runtime~main.b8885f13.js b/assets/js/runtime~main.c17fd005.js similarity index 98% rename from assets/js/runtime~main.b8885f13.js rename to assets/js/runtime~main.c17fd005.js index 5d320601..ccc6b7f3 100644 --- a/assets/js/runtime~main.b8885f13.js +++ b/assets/js/runtime~main.c17fd005.js @@ -1 +1 @@ -(()=>{"use strict";var e,a,c,f,d,b={},r={};function t(e){var a=r[e];if(void 0!==a)return a.exports;var c=r[e]={exports:{}};return b[e].call(c.exports,c,c.exports,t),c.exports}t.m=b,e=[],t.O=(a,c,f,d)=>{if(!c){var b=1/0;for(i=0;i=d)&&Object.keys(t.O).every((e=>t.O[e](c[o])))?c.splice(o--,1):(r=!1,d0&&e[i-1][2]>d;i--)e[i]=e[i-1];e[i]=[c,f,d]},t.n=e=>{var a=e&&e.__esModule?()=>e.default:()=>e;return t.d(a,{a:a}),a},c=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,t.t=function(e,f){if(1&f&&(e=this(e)),8&f)return e;if("object"==typeof e&&e){if(4&f&&e.__esModule)return e;if(16&f&&"function"==typeof e.then)return e}var d=Object.create(null);t.r(d);var b={};a=a||[null,c({}),c([]),c(c)];for(var r=2&f&&e;"object"==typeof r&&!~a.indexOf(r);r=c(r))Object.getOwnPropertyNames(r).forEach((a=>b[a]=()=>e[a]));return b.default=()=>e,t.d(d,b),d},t.d=(e,a)=>{for(var c in a)t.o(a,c)&&!t.o(e,c)&&Object.defineProperty(e,c,{enumerable:!0,get:a[c]})},t.f={},t.e=e=>Promise.all(Object.keys(t.f).reduce(((a,c)=>(t.f[c](e,a),a)),[])),t.u=e=>"assets/js/"+({1:"ff0b8175",183:"96083bb9",186:"a8521eb9",196:"505d7517",234:"39838d4e",403:"3ad77611",814:"95f4d37c",825:"7be5f79d",860:"2cd8ab24",1217:"1e5c498d",1340:"e2299c6d",1420:"6741c1a9",1549:"1506d638",1585:"2450005c",1666:"c401bc0d",1744:"10921c5b",1937:"49882d99",2446:"bc1f8660",2522:"c7c467a1",2535:"814f3328",2743:"968b4846",2889:"a1e7621f",2967:"e4eb6786",3085:"1f391b9e",3089:"a6aa9e1f",3560:"17d8eee1",3608:"9e4087bc",3629:"aba21aa0",3844:"772c3429",4e3:"afc29949",4031:"f81c1134",4057:"c03baef0",4063:"31caa863",4076:"7d1225b6",4088:"0058b4c6",4195:"c4f5d8e4",4368:"a94703ab",4524:"e27695c2",4713:"bc03b1b7",5014:"38346c4b",5857:"3e240fbf",5980:"a7456010",6103:"ccc49370",6142:"bebdd554",6265:"906ac375",6414:"3d832522",6427:"59628a4d",6642:"c15d9823",6800:"2f9db241",6906:"9ebba4ea",6957:"a5b8d3e9",6974:"af21c641",7222:"0bd3a280",7346:"f3759001",7393:"acecf23e",7414:"393be207",7540:"0f1af657",7664:"reactPlayerPreview",7918:"17896441",7937:"c48bbb24",8294:"3463d78f",8392:"ed0568ab",8518:"a7bd4aaa",8653:"ec0f34d7",8754:"6b1fc3de",9208:"36994c47",9256:"3bea7cd1",9268:"ba771284",9572:"7d56ced7",9586:"3b4579e8",9601:"18fc9463",9661:"5e95c892",9866:"11f6a8a1",9929:"1863cff0"}[e]||e)+"."+{1:"a06d7a3a",183:"d2117bbc",186:"4c804139",196:"e701aaa5",234:"76946f78",403:"dafd6774",814:"4e08262b",825:"d42ee47b",860:"d7028e73",1217:"da539c3e",1340:"11ae0623",1420:"aeb48ccb",1549:"7d1c392d",1585:"db463b44",1666:"ffe69db6",1744:"4d956c8c",1772:"3d06e0e2",1937:"96ed6c84",2446:"74e6e406",2522:"79a7edaa",2535:"0ba125c5",2700:"83aa9a75",2743:"67c47945",2889:"4e529702",2967:"a7c4ab03",3085:"3dba5538",3089:"911b8dd5",3560:"cffc9f0b",3608:"ee0c677a",3629:"eb980bea",3844:"84e52c31",4e3:"1038aca8",4031:"6f8546d9",4057:"efd80a40",4063:"ead42224",4076:"82c0a513",4088:"68a1efdf",4195:"b92bf9f8",4368:"4aef8496",4524:"f13327a6",4713:"802bf1c8",5014:"bd156c61",5655:"ab3e12ff",5857:"241b5abc",5980:"f93cbc61",6103:"4990621e",6142:"9adb781d",6265:"ecc041e6",6414:"19d9c76b",6427:"2704a200",6642:"b756708b",6800:"2dc6905e",6906:"df274206",6957:"07f3c936",6974:"242c3e8d",7222:"7b0ad8f2",7346:"ecb01d32",7393:"9d35c647",7414:"37f8208f",7540:"5d4247b4",7664:"e5a1011e",7918:"b96e81ff",7937:"881a7b56",8041:"e22d43c6",8294:"97dc70b8",8392:"a5d4194d",8518:"eaa77d27",8653:"1c9ade88",8754:"f012b88f",9208:"203bad01",9256:"0698ba6b",9268:"f1b885cf",9572:"3bfef52c",9586:"82224d7d",9601:"e6c986e7",9661:"2dcb0623",9866:"6c00f0dc",9929:"e36f6a83"}[e]+".js",t.miniCssF=e=>{},t.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),t.o=(e,a)=>Object.prototype.hasOwnProperty.call(e,a),f={},d="geogardenclub-github-io:",t.l=(e,a,c,b)=>{if(f[e])f[e].push(a);else{var r,o;if(void 0!==c)for(var n=document.getElementsByTagName("script"),i=0;i{r.onerror=r.onload=null,clearTimeout(s);var d=f[e];if(delete f[e],r.parentNode&&r.parentNode.removeChild(r),d&&d.forEach((e=>e(c))),a)return a(c)},s=setTimeout(l.bind(null,void 0,{type:"timeout",target:r}),12e4);r.onerror=l.bind(null,r.onerror),r.onload=l.bind(null,r.onload),o&&document.head.appendChild(r)}},t.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},t.p="/",t.gca=function(e){return e={17896441:"7918",ff0b8175:"1","96083bb9":"183",a8521eb9:"186","505d7517":"196","39838d4e":"234","3ad77611":"403","95f4d37c":"814","7be5f79d":"825","2cd8ab24":"860","1e5c498d":"1217",e2299c6d:"1340","6741c1a9":"1420","1506d638":"1549","2450005c":"1585",c401bc0d:"1666","10921c5b":"1744","49882d99":"1937",bc1f8660:"2446",c7c467a1:"2522","814f3328":"2535","968b4846":"2743",a1e7621f:"2889",e4eb6786:"2967","1f391b9e":"3085",a6aa9e1f:"3089","17d8eee1":"3560","9e4087bc":"3608",aba21aa0:"3629","772c3429":"3844",afc29949:"4000",f81c1134:"4031",c03baef0:"4057","31caa863":"4063","7d1225b6":"4076","0058b4c6":"4088",c4f5d8e4:"4195",a94703ab:"4368",e27695c2:"4524",bc03b1b7:"4713","38346c4b":"5014","3e240fbf":"5857",a7456010:"5980",ccc49370:"6103",bebdd554:"6142","906ac375":"6265","3d832522":"6414","59628a4d":"6427",c15d9823:"6642","2f9db241":"6800","9ebba4ea":"6906",a5b8d3e9:"6957",af21c641:"6974","0bd3a280":"7222",f3759001:"7346",acecf23e:"7393","393be207":"7414","0f1af657":"7540",reactPlayerPreview:"7664",c48bbb24:"7937","3463d78f":"8294",ed0568ab:"8392",a7bd4aaa:"8518",ec0f34d7:"8653","6b1fc3de":"8754","36994c47":"9208","3bea7cd1":"9256",ba771284:"9268","7d56ced7":"9572","3b4579e8":"9586","18fc9463":"9601","5e95c892":"9661","11f6a8a1":"9866","1863cff0":"9929"}[e]||e,t.p+t.u(e)},(()=>{var e={1303:0,532:0};t.f.j=(a,c)=>{var f=t.o(e,a)?e[a]:void 0;if(0!==f)if(f)c.push(f[2]);else if(/^(1303|532)$/.test(a))e[a]=0;else{var d=new Promise(((c,d)=>f=e[a]=[c,d]));c.push(f[2]=d);var b=t.p+t.u(a),r=new Error;t.l(b,(c=>{if(t.o(e,a)&&(0!==(f=e[a])&&(e[a]=void 0),f)){var d=c&&("load"===c.type?"missing":c.type),b=c&&c.target&&c.target.src;r.message="Loading chunk "+a+" failed.\n("+d+": "+b+")",r.name="ChunkLoadError",r.type=d,r.request=b,f[1](r)}}),"chunk-"+a,a)}},t.O.j=a=>0===e[a];var a=(a,c)=>{var f,d,b=c[0],r=c[1],o=c[2],n=0;if(b.some((a=>0!==e[a]))){for(f in r)t.o(r,f)&&(t.m[f]=r[f]);if(o)var i=o(t)}for(a&&a(c);n{"use strict";var e,a,c,f,d,b={},r={};function t(e){var a=r[e];if(void 0!==a)return a.exports;var c=r[e]={exports:{}};return b[e].call(c.exports,c,c.exports,t),c.exports}t.m=b,e=[],t.O=(a,c,f,d)=>{if(!c){var b=1/0;for(i=0;i=d)&&Object.keys(t.O).every((e=>t.O[e](c[o])))?c.splice(o--,1):(r=!1,d0&&e[i-1][2]>d;i--)e[i]=e[i-1];e[i]=[c,f,d]},t.n=e=>{var a=e&&e.__esModule?()=>e.default:()=>e;return t.d(a,{a:a}),a},c=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,t.t=function(e,f){if(1&f&&(e=this(e)),8&f)return e;if("object"==typeof e&&e){if(4&f&&e.__esModule)return e;if(16&f&&"function"==typeof e.then)return e}var d=Object.create(null);t.r(d);var b={};a=a||[null,c({}),c([]),c(c)];for(var r=2&f&&e;"object"==typeof r&&!~a.indexOf(r);r=c(r))Object.getOwnPropertyNames(r).forEach((a=>b[a]=()=>e[a]));return b.default=()=>e,t.d(d,b),d},t.d=(e,a)=>{for(var c in a)t.o(a,c)&&!t.o(e,c)&&Object.defineProperty(e,c,{enumerable:!0,get:a[c]})},t.f={},t.e=e=>Promise.all(Object.keys(t.f).reduce(((a,c)=>(t.f[c](e,a),a)),[])),t.u=e=>"assets/js/"+({1:"ff0b8175",183:"96083bb9",186:"a8521eb9",196:"505d7517",234:"39838d4e",403:"3ad77611",814:"95f4d37c",825:"7be5f79d",860:"2cd8ab24",1217:"1e5c498d",1340:"e2299c6d",1420:"6741c1a9",1549:"1506d638",1585:"2450005c",1666:"c401bc0d",1744:"10921c5b",1937:"49882d99",2446:"bc1f8660",2522:"c7c467a1",2535:"814f3328",2743:"968b4846",2889:"a1e7621f",2967:"e4eb6786",3085:"1f391b9e",3089:"a6aa9e1f",3560:"17d8eee1",3608:"9e4087bc",3629:"aba21aa0",3844:"772c3429",4e3:"afc29949",4031:"f81c1134",4057:"c03baef0",4063:"31caa863",4076:"7d1225b6",4088:"0058b4c6",4195:"c4f5d8e4",4368:"a94703ab",4524:"e27695c2",4713:"bc03b1b7",5014:"38346c4b",5857:"3e240fbf",5980:"a7456010",6103:"ccc49370",6142:"bebdd554",6265:"906ac375",6414:"3d832522",6427:"59628a4d",6642:"c15d9823",6800:"2f9db241",6906:"9ebba4ea",6957:"a5b8d3e9",6974:"af21c641",7222:"0bd3a280",7346:"f3759001",7393:"acecf23e",7414:"393be207",7540:"0f1af657",7664:"reactPlayerPreview",7918:"17896441",7937:"c48bbb24",8294:"3463d78f",8392:"ed0568ab",8518:"a7bd4aaa",8653:"ec0f34d7",8754:"6b1fc3de",9208:"36994c47",9256:"3bea7cd1",9268:"ba771284",9572:"7d56ced7",9586:"3b4579e8",9601:"18fc9463",9661:"5e95c892",9866:"11f6a8a1",9929:"1863cff0"}[e]||e)+"."+{1:"a06d7a3a",183:"d2117bbc",186:"4c804139",196:"e701aaa5",234:"76946f78",403:"dafd6774",814:"4e08262b",825:"d42ee47b",860:"d7028e73",1217:"8ef05fb3",1340:"11ae0623",1420:"aeb48ccb",1549:"7d1c392d",1585:"db463b44",1666:"ffe69db6",1744:"4d956c8c",1772:"3d06e0e2",1937:"96ed6c84",2446:"74e6e406",2522:"79a7edaa",2535:"0ba125c5",2700:"83aa9a75",2743:"67c47945",2889:"4e529702",2967:"a7c4ab03",3085:"3dba5538",3089:"911b8dd5",3560:"cffc9f0b",3608:"ee0c677a",3629:"eb980bea",3844:"84e52c31",4e3:"1038aca8",4031:"6f8546d9",4057:"efd80a40",4063:"ead42224",4076:"82c0a513",4088:"756e19e2",4195:"b92bf9f8",4368:"4aef8496",4524:"f13327a6",4713:"802bf1c8",5014:"bd156c61",5655:"ab3e12ff",5857:"241b5abc",5980:"f93cbc61",6103:"4990621e",6142:"9adb781d",6265:"ecc041e6",6414:"19d9c76b",6427:"2704a200",6642:"b756708b",6800:"2dc6905e",6906:"df274206",6957:"07f3c936",6974:"242c3e8d",7222:"7b0ad8f2",7346:"ecb01d32",7393:"9d35c647",7414:"37f8208f",7540:"5d4247b4",7664:"e5a1011e",7918:"b96e81ff",7937:"881a7b56",8041:"e22d43c6",8294:"97dc70b8",8392:"a5d4194d",8518:"eaa77d27",8653:"1c9ade88",8754:"f012b88f",9208:"203bad01",9256:"0698ba6b",9268:"f1b885cf",9572:"3bfef52c",9586:"82224d7d",9601:"e6c986e7",9661:"2dcb0623",9866:"6c00f0dc",9929:"e36f6a83"}[e]+".js",t.miniCssF=e=>{},t.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),t.o=(e,a)=>Object.prototype.hasOwnProperty.call(e,a),f={},d="geogardenclub-github-io:",t.l=(e,a,c,b)=>{if(f[e])f[e].push(a);else{var r,o;if(void 0!==c)for(var n=document.getElementsByTagName("script"),i=0;i{r.onerror=r.onload=null,clearTimeout(s);var d=f[e];if(delete f[e],r.parentNode&&r.parentNode.removeChild(r),d&&d.forEach((e=>e(c))),a)return a(c)},s=setTimeout(l.bind(null,void 0,{type:"timeout",target:r}),12e4);r.onerror=l.bind(null,r.onerror),r.onload=l.bind(null,r.onload),o&&document.head.appendChild(r)}},t.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},t.p="/",t.gca=function(e){return e={17896441:"7918",ff0b8175:"1","96083bb9":"183",a8521eb9:"186","505d7517":"196","39838d4e":"234","3ad77611":"403","95f4d37c":"814","7be5f79d":"825","2cd8ab24":"860","1e5c498d":"1217",e2299c6d:"1340","6741c1a9":"1420","1506d638":"1549","2450005c":"1585",c401bc0d:"1666","10921c5b":"1744","49882d99":"1937",bc1f8660:"2446",c7c467a1:"2522","814f3328":"2535","968b4846":"2743",a1e7621f:"2889",e4eb6786:"2967","1f391b9e":"3085",a6aa9e1f:"3089","17d8eee1":"3560","9e4087bc":"3608",aba21aa0:"3629","772c3429":"3844",afc29949:"4000",f81c1134:"4031",c03baef0:"4057","31caa863":"4063","7d1225b6":"4076","0058b4c6":"4088",c4f5d8e4:"4195",a94703ab:"4368",e27695c2:"4524",bc03b1b7:"4713","38346c4b":"5014","3e240fbf":"5857",a7456010:"5980",ccc49370:"6103",bebdd554:"6142","906ac375":"6265","3d832522":"6414","59628a4d":"6427",c15d9823:"6642","2f9db241":"6800","9ebba4ea":"6906",a5b8d3e9:"6957",af21c641:"6974","0bd3a280":"7222",f3759001:"7346",acecf23e:"7393","393be207":"7414","0f1af657":"7540",reactPlayerPreview:"7664",c48bbb24:"7937","3463d78f":"8294",ed0568ab:"8392",a7bd4aaa:"8518",ec0f34d7:"8653","6b1fc3de":"8754","36994c47":"9208","3bea7cd1":"9256",ba771284:"9268","7d56ced7":"9572","3b4579e8":"9586","18fc9463":"9601","5e95c892":"9661","11f6a8a1":"9866","1863cff0":"9929"}[e]||e,t.p+t.u(e)},(()=>{var e={1303:0,532:0};t.f.j=(a,c)=>{var f=t.o(e,a)?e[a]:void 0;if(0!==f)if(f)c.push(f[2]);else if(/^(1303|532)$/.test(a))e[a]=0;else{var d=new Promise(((c,d)=>f=e[a]=[c,d]));c.push(f[2]=d);var b=t.p+t.u(a),r=new Error;t.l(b,(c=>{if(t.o(e,a)&&(0!==(f=e[a])&&(e[a]=void 0),f)){var d=c&&("load"===c.type?"missing":c.type),b=c&&c.target&&c.target.src;r.message="Loading chunk "+a+" failed.\n("+d+": "+b+")",r.name="ChunkLoadError",r.type=d,r.request=b,f[1](r)}}),"chunk-"+a,a)}},t.O.j=a=>0===e[a];var a=(a,c)=>{var f,d,b=c[0],r=c[1],o=c[2],n=0;if(b.some((a=>0!==e[a]))){for(f in r)t.o(r,f)&&(t.m[f]=r[f]);if(o)var i=o(t)}for(a&&a(c);n Blog | Geo Garden Club - + diff --git a/blog/2023/02/10/welcome.html b/blog/2023/02/10/welcome.html index 7371e9bb..fc872d63 100644 --- a/blog/2023/02/10/welcome.html +++ b/blog/2023/02/10/welcome.html @@ -5,7 +5,7 @@ Welcome, Geo Garden Club! Aloha, Agile Garden Club! | Geo Garden Club - + diff --git a/blog/archive.html b/blog/archive.html index 84db5fec..643a117b 100644 --- a/blog/archive.html +++ b/blog/archive.html @@ -5,7 +5,7 @@ Archive | Geo Garden Club - + diff --git a/docs/business.html b/docs/business.html index 2cfdbd04..6ce69f8c 100644 --- a/docs/business.html +++ b/docs/business.html @@ -5,7 +5,7 @@ Welcome to the GGC Business Development Guide | Geo Garden Club - + diff --git a/docs/business/market-size.html b/docs/business/market-size.html index f7e31212..fadd1504 100644 --- a/docs/business/market-size.html +++ b/docs/business/market-size.html @@ -5,7 +5,7 @@ Market Size Estimation (USA) | Geo Garden Club - + diff --git a/docs/business/milestones.html b/docs/business/milestones.html index b809b955..10101403 100644 --- a/docs/business/milestones.html +++ b/docs/business/milestones.html @@ -5,7 +5,7 @@ Milestones | Geo Garden Club - + diff --git a/docs/business/roadmap.html b/docs/business/roadmap.html index dcfd627e..e5d91c6b 100644 --- a/docs/business/roadmap.html +++ b/docs/business/roadmap.html @@ -5,7 +5,7 @@ Roadmap | Geo Garden Club - + diff --git a/docs/develop.html b/docs/develop.html index 453cc499..1098dd4e 100644 --- a/docs/develop.html +++ b/docs/develop.html @@ -5,7 +5,7 @@ Welcome to the GGC Developers Guide | Geo Garden Club - + diff --git a/docs/develop/architecture.html b/docs/develop/architecture.html index 29925fbb..61ed7a38 100644 --- a/docs/develop/architecture.html +++ b/docs/develop/architecture.html @@ -5,7 +5,7 @@ Architecture | Geo Garden Club - + diff --git a/docs/develop/backups.html b/docs/develop/backups.html index 11cbd85f..c0a8efa7 100644 --- a/docs/develop/backups.html +++ b/docs/develop/backups.html @@ -5,7 +5,7 @@ Backups | Geo Garden Club - + diff --git a/docs/develop/coding-standards.html b/docs/develop/coding-standards.html index 68261735..2b064d4a 100644 --- a/docs/develop/coding-standards.html +++ b/docs/develop/coding-standards.html @@ -5,7 +5,7 @@ Coding Standards | Geo Garden Club - + diff --git a/docs/develop/deployment.html b/docs/develop/deployment.html index 40043029..921fee87 100644 --- a/docs/develop/deployment.html +++ b/docs/develop/deployment.html @@ -5,7 +5,7 @@ Deployment | Geo Garden Club - + diff --git a/docs/develop/design/badges.html b/docs/develop/design/badges.html index 2604120b..761d2b76 100644 --- a/docs/develop/design/badges.html +++ b/docs/develop/design/badges.html @@ -5,7 +5,7 @@ Badges | Geo Garden Club - + diff --git a/docs/develop/design/data-model-old.html b/docs/develop/design/data-model-old.html index 2f521bf8..b1f0ac1e 100644 --- a/docs/develop/design/data-model-old.html +++ b/docs/develop/design/data-model-old.html @@ -5,7 +5,7 @@ Data Model | Geo Garden Club - + diff --git a/docs/develop/design/data-model.html b/docs/develop/design/data-model.html index 7a5aeb9b..c9b977dc 100644 --- a/docs/develop/design/data-model.html +++ b/docs/develop/design/data-model.html @@ -5,7 +5,7 @@ Data Model | Geo Garden Club - + diff --git a/docs/develop/design/data-mutation.html b/docs/develop/design/data-mutation.html index 6d2f3c7c..2324d386 100644 --- a/docs/develop/design/data-mutation.html +++ b/docs/develop/design/data-mutation.html @@ -5,7 +5,7 @@ Data Mutation | Geo Garden Club - + diff --git a/docs/develop/design/input-fields.html b/docs/develop/design/input-fields.html index a6c6625b..a9963483 100644 --- a/docs/develop/design/input-fields.html +++ b/docs/develop/design/input-fields.html @@ -5,7 +5,7 @@ GGC Input Fields | Geo Garden Club - + diff --git a/docs/develop/design/with-widgets.html b/docs/develop/design/with-widgets.html index 1c99fc3c..4c48f964 100644 --- a/docs/develop/design/with-widgets.html +++ b/docs/develop/design/with-widgets.html @@ -5,7 +5,7 @@ "With" widgets | Geo Garden Club - + diff --git a/docs/develop/installation.html b/docs/develop/installation.html index a1bed424..8cca8984 100644 --- a/docs/develop/installation.html +++ b/docs/develop/installation.html @@ -5,7 +5,7 @@ Installation | Geo Garden Club - + diff --git a/docs/develop/onboarding.html b/docs/develop/onboarding.html index 0f6fa0ff..da89420a 100644 --- a/docs/develop/onboarding.html +++ b/docs/develop/onboarding.html @@ -5,7 +5,7 @@ Onboarding | Geo Garden Club - + diff --git a/docs/develop/releases/release-0.0/chatgpt-feedback.html b/docs/develop/releases/release-0.0/chatgpt-feedback.html index aa808f59..82b5bdf7 100644 --- a/docs/develop/releases/release-0.0/chatgpt-feedback.html +++ b/docs/develop/releases/release-0.0/chatgpt-feedback.html @@ -5,7 +5,7 @@ ChatGPT feedback | Geo Garden Club - + diff --git a/docs/develop/releases/release-0.0/customer-feedback.html b/docs/develop/releases/release-0.0/customer-feedback.html index da1762c6..cbd2ff5f 100644 --- a/docs/develop/releases/release-0.0/customer-feedback.html +++ b/docs/develop/releases/release-0.0/customer-feedback.html @@ -5,7 +5,7 @@ Customer feedback | Geo Garden Club - + diff --git a/docs/develop/releases/release-0.0/design.html b/docs/develop/releases/release-0.0/design.html index d57535a8..b4eec871 100644 --- a/docs/develop/releases/release-0.0/design.html +++ b/docs/develop/releases/release-0.0/design.html @@ -5,7 +5,7 @@ Design and implementation | Geo Garden Club - + diff --git a/docs/develop/releases/release-0.0/entrepreneur-feedback.html b/docs/develop/releases/release-0.0/entrepreneur-feedback.html index a7d7a6b5..0abfa896 100644 --- a/docs/develop/releases/release-0.0/entrepreneur-feedback.html +++ b/docs/develop/releases/release-0.0/entrepreneur-feedback.html @@ -5,7 +5,7 @@ Entrepreneur feedback | Geo Garden Club - + diff --git a/docs/develop/releases/release-1.0/cvp.html b/docs/develop/releases/release-1.0/cvp.html index 6f3a3821..433160ee 100644 --- a/docs/develop/releases/release-1.0/cvp.html +++ b/docs/develop/releases/release-1.0/cvp.html @@ -5,7 +5,7 @@ Core Value Propositions | Geo Garden Club - + diff --git a/docs/develop/releases/release-1.0/end-of-season-feedback.html b/docs/develop/releases/release-1.0/end-of-season-feedback.html index 57638ff3..1e24dd37 100644 --- a/docs/develop/releases/release-1.0/end-of-season-feedback.html +++ b/docs/develop/releases/release-1.0/end-of-season-feedback.html @@ -5,7 +5,7 @@ End of Season Feedback | Geo Garden Club - + diff --git a/docs/develop/releases/release-1.0/goals.html b/docs/develop/releases/release-1.0/goals.html index 2a46f2b5..fe1b82a0 100644 --- a/docs/develop/releases/release-1.0/goals.html +++ b/docs/develop/releases/release-1.0/goals.html @@ -5,7 +5,7 @@ Technology Goals | Geo Garden Club - + diff --git a/docs/develop/releases/release-1.0/onboarding-feedback.html b/docs/develop/releases/release-1.0/onboarding-feedback.html index 64a69a8e..920a676b 100644 --- a/docs/develop/releases/release-1.0/onboarding-feedback.html +++ b/docs/develop/releases/release-1.0/onboarding-feedback.html @@ -5,7 +5,7 @@ Onboarding Feedback | Geo Garden Club - + diff --git a/docs/develop/scripts.html b/docs/develop/scripts.html index 3185b41e..b40bb7f4 100644 --- a/docs/develop/scripts.html +++ b/docs/develop/scripts.html @@ -5,7 +5,7 @@ Scripts | Geo Garden Club - + diff --git a/docs/develop/testing.html b/docs/develop/testing.html index 67b9760b..847426a1 100644 --- a/docs/develop/testing.html +++ b/docs/develop/testing.html @@ -3,14 +3,19 @@ -Testing | Geo Garden Club +Testing | Geo Garden Club - +

Testing

-

This page documents our current approach to testing the GeoGardenClub app.

+

The current goal of testing in GeoGardenClub is to prevent catastrophic regression. In other words, we want our tests to ensure that changes to the code do not result in an app where important features no longer work. This means that our test suite should ensure that:

+
    +
  • All commonly accessed screens display without error. (The tests might not check screens that are displayed "rarely", such as those resulting from anomalous conditions like network instability.)
  • +
  • CRUD operations on entities can be performed successfully when available.
  • +
  • Buttons on all commonly accessed screens, when tapped, do not generate an error, and the resulting screen is checked to see that at least some of the intended results are displayed.
  • +

Run the tests​

To run the test suite, invoke ./run_tests.sh. It should produce output similar to the following:

~/GitHub/geogardenclub/ggc_app git:[issue-235]
./run_tests.sh
+ flutter test integration_test/app_test.dart --coverage
00:15 +0: loading /Users/philipjohnson/GitHub/geogardenclub/ggc_app/integration_test/app_test.dart Ru00:41 +0: loading /Users/philipjohnson/GitHub/geogardenclub/ggc_app/integration_test/app_test.dart
00:48 +0: loading /Users/philipjohnson/GitHub/geogardenclub/ggc_app/integration_test/app_test.dart 6.6s
Xcode build done. 33.2s
00:54 +0: GGC Integration Test (All) Fixture 1 Tests
Testing admin feature
Testing badge feature
Testing chapter feature
Testing chat feature
Testing crop feature
Testing garden feature
Testing gardener feature
Testing geobot feature
Testing home feature
Testing observation feature
Testing outcome feature
Testing planting feature
Testing settings feature
Testing task feature
Testing variety feature
02:00 +1: All tests passed!
+ genhtml -q coverage/lcov.info -o coverage/html
Overall coverage rate:
source files: 472
lines.......: 35.0% (4464 of 12740 lines)
functions...: no data found
Message summary:
no messages were reported
@@ -21,8 +26,8 @@

Run the tests<
  • Our test architecture is organized around features.
  • We compute coverage as a simple check on the quality of the test set. We do not strive for 100% coverage, but we can clearly do better than (say) 35%. In addition, the coverage report provides an efficient way to find important areas of the code base that have not yet been tested.
  • -

    Always watch the iOS simulator!​

    -
    warning

    If testing with the iOS simulator, the testing process will occasionally (and unpredictably) pause waiting for you to click on a button to allow pasting:

    For this reason, it's important to always watch the simulator at least until the tests start, because you might need to click a button to let the tests proceed.

    This is a security feature in the iOS operating system. I do not know of a way to disable it.

    +

    Always monitor the iOS simulator!​

    +
    warning

    If testing with the iOS simulator, the testing process will occasionally (and unpredictably) pause waiting for you to click on a button to allow pasting:

    For this reason, it's important to always monitor the simulator at least until the tests start, because you might need to click a button to let the tests proceed. Otherwise the test process will hang indefinitely.

    This is a security feature in the iOS operating system. There is apparently no way to disable it at the current time.

    About app_test.dart​

    To further understand the test process, it's helpful to review the code that is run by the ./run_tests.sh command:

    // integration_test/app_test.dart
    void main() {
    IntegrationTestWidgetsFlutterBinding.ensureInitialized();
    group('GGC Integration Test (All)', () {
    patrolWidgetTest('Fixture 1 Tests', (PatrolTester $) async {
    await Firebase.initializeApp();
    setFirebaseUiIsTestMode(true);
    FirebaseAuth mockAuth = MockFirebaseAuth();
    String email = 'jennacorindeane@gmail.com';
    mockAuth.createUserWithEmailAndPassword(email: email, password: '');
    TestFixture testFixture = await TestFixture.getInstance(testFixture1Path);
    await $.pumpWidgetAndSettle(ProviderScope(
    overrides: [
    firebaseAuthProvider.overrideWithValue(mockAuth),
    badgesProvider.overrideWith((_) => testFixture.getBadgesStream()),
    badgeDatabaseProvider.overrideWith((_) => testFixture.getBadgeDatabase()),
    badgeInstancesProvider.overrideWith((_) => testFixture.getBadgeInstancesStream()),
    badgeInstanceDatabaseProvider.overrideWith((_) => testFixture.getBadgeInstanceDatabase()),
    bedsProvider.overrideWith((_) => testFixture.getBedsStream()),
    bedDatabaseProvider.overrideWith((ref) => testFixture.getBedDatabase()),
    chaptersProvider.overrideWith((_) => testFixture.getChaptersStream()),
    chapterDatabaseProvider.overrideWith((_) => testFixture.getChapterDatabase()),
    chatRoomDatabaseProvider.overrideWith((_) => testFixture.getChatRoomDatabase()),
    chatUserDatabaseProvider.overrideWith((_) => testFixture.getChatUserDatabase()),
    cropsProvider.overrideWith((_) => testFixture.getCropsStream()),
    cropDatabaseProvider.overrideWith((_) => testFixture.getCropDatabase()),
    editorsProvider.overrideWith((_) => testFixture.getEditorsStream()),
    editorDatabaseProvider.overrideWith((_) => testFixture.getEditorDatabase()),
    familiesProvider.overrideWith((_) => testFixture.getFamiliesStream()),
    familyDatabaseProvider.overrideWith((_) => testFixture.getFamilyDatabase()),
    gardensProvider.overrideWith((_) => testFixture.getGardensStream()),
    gardenDatabaseProvider.overrideWith((_) => testFixture.getGardenDatabase()),
    gardenersProvider.overrideWith((_) => testFixture.getGardenersStream()),
    gardenerDatabaseProvider.overrideWith((_) => testFixture.getGardenerDatabase()),
    observationsProvider.overrideWith((_) => testFixture.getObservationsStream()),
    observationDatabaseProvider.overrideWith((_) => testFixture.getObservationDatabase()),
    outcomesProvider.overrideWith((_) => testFixture.getOutcomesStream()),
    outcomeDatabaseProvider.overrideWith((_) => testFixture.getOutcomeDatabase()),
    plantingsProvider.overrideWith((_) => testFixture.getPlantingsStream()),
    plantingDatabaseProvider.overrideWith((_) => testFixture.getPlantingDatabase()),
    rolesProvider.overrideWith((_) => testFixture.getRolesStream()),
    roleDatabaseProvider.overrideWith((_) => testFixture.getRoleDatabase()),
    tagsProvider.overrideWith((_) => testFixture.getTagsStream()),
    tagDatabaseProvider.overrideWith((_) => testFixture.getTagDatabase()),
    tasksProvider.overrideWith((_) => testFixture.getTasksStream()),
    taskDatabaseProvider.overrideWith((_) => testFixture.getTaskDatabase()),
    usersProvider.overrideWith((_) => testFixture.getUsersStream()),
    userDatabaseProvider.overrideWith((_) => testFixture.getUserDatabase()),
    varietiesProvider.overrideWith((_) => testFixture.getVarietiesStream()),
    varietyDatabaseProvider.overrideWith((_) => testFixture.getVarietyDatabase()),
    ],
    child: const MyApp(),
    ));
    expect($(HomeScreen).visible, equals(true), reason: 'Login fails');
    await checkIntegrity($, reason: 'startup');
    await testAdmin($);
    await checkIntegrity($, reason: 'admin feature');
    await testBadge($);
    await checkIntegrity($, reason: 'badge feature');
    await testChapter($);
    await checkIntegrity($, reason: 'chapter feature');
    await testChat($);
    await checkIntegrity($, reason: 'chat feature');
    await testCrop($);
    await checkIntegrity($, reason: 'crop feature');
    await testGarden($);
    await checkIntegrity($, reason: 'garden feature');
    await testGardener($);
    await checkIntegrity($, reason: 'gardener feature');
    await testGeoBot($);
    await checkIntegrity($, reason: 'geobot feature');
    await testHome($);
    await checkIntegrity($, reason: 'home feature');
    await testObservation($);
    await checkIntegrity($, reason: 'observation feature');
    await testOutcome($);
    await checkIntegrity($, reason: 'outcome feature');
    await testPlanting($);
    await checkIntegrity($, reason: 'planting feature');
    await testSettings($);
    await checkIntegrity($, reason: 'settings feature');
    await testTask($);
    await checkIntegrity($, reason: 'task feature');
    await testVariety($);
    await checkIntegrity($, reason: 'variety feature');
    });
    });
    }
    @@ -130,6 +135,6 @@

    TestFi
    • get<Entity>Stream() - returns a Stream of the List of the entities from the test fixture.
    • get<Entity>Database() - returns The Fixture<Entity>Database from the test fixture.
    • -

    + \ No newline at end of file diff --git a/docs/home/food-security.html b/docs/home/food-security.html index 33e08060..1855ce6a 100644 --- a/docs/home/food-security.html +++ b/docs/home/food-security.html @@ -5,7 +5,7 @@ Food Security | Geo Garden Club - + diff --git a/docs/home/innovations.html b/docs/home/innovations.html index b6f49e92..fa8e4c5a 100644 --- a/docs/home/innovations.html +++ b/docs/home/innovations.html @@ -5,7 +5,7 @@ Design Innovations | Geo Garden Club - + diff --git a/docs/home/related-work.html b/docs/home/related-work.html index a1dc91bd..2a011d9d 100644 --- a/docs/home/related-work.html +++ b/docs/home/related-work.html @@ -5,7 +5,7 @@ Garden Planning Tools | Geo Garden Club - + diff --git a/docs/home/serious-gardeners.html b/docs/home/serious-gardeners.html index 30b2261b..af9defcc 100644 --- a/docs/home/serious-gardeners.html +++ b/docs/home/serious-gardeners.html @@ -5,7 +5,7 @@ "Serious" Gardeners | Geo Garden Club - + diff --git a/docs/home/sneak-peek.html b/docs/home/sneak-peek.html index e25469cb..030d9c27 100644 --- a/docs/home/sneak-peek.html +++ b/docs/home/sneak-peek.html @@ -5,7 +5,7 @@ Mobile App Sneak Peek | Geo Garden Club - + diff --git a/docs/home/team.html b/docs/home/team.html index 7dd39be7..deb927d8 100644 --- a/docs/home/team.html +++ b/docs/home/team.html @@ -5,7 +5,7 @@ The Team | Geo Garden Club - + diff --git a/docs/home/welcome.html b/docs/home/welcome.html index aa553c5c..4b9e9ea7 100644 --- a/docs/home/welcome.html +++ b/docs/home/welcome.html @@ -5,7 +5,7 @@ Welcome | Geo Garden Club - + diff --git a/docs/user-guide/adding-plantings.html b/docs/user-guide/adding-plantings.html index 167fcea7..370850f5 100644 --- a/docs/user-guide/adding-plantings.html +++ b/docs/user-guide/adding-plantings.html @@ -5,7 +5,7 @@ Add Plantings to Beds | Geo Garden Club - + diff --git a/docs/user-guide/adding-vendors-crops-varieties.html b/docs/user-guide/adding-vendors-crops-varieties.html index 5e13a9b9..8cb0f24a 100644 --- a/docs/user-guide/adding-vendors-crops-varieties.html +++ b/docs/user-guide/adding-vendors-crops-varieties.html @@ -5,7 +5,7 @@ Add Crops, Varieties, Vendors to the Chapter Database | Geo Garden Club - + diff --git a/docs/user-guide/badges.html b/docs/user-guide/badges.html index 784a069f..1887bb3d 100644 --- a/docs/user-guide/badges.html +++ b/docs/user-guide/badges.html @@ -5,7 +5,7 @@ Badges | Geo Garden Club - + diff --git a/docs/user-guide/chat-rooms.html b/docs/user-guide/chat-rooms.html index 8030b975..4c1899a8 100644 --- a/docs/user-guide/chat-rooms.html +++ b/docs/user-guide/chat-rooms.html @@ -5,7 +5,7 @@ Chat Rooms | Geo Garden Club - + diff --git a/docs/user-guide/define-a-garden.html b/docs/user-guide/define-a-garden.html index e56905a5..c5dd0c92 100644 --- a/docs/user-guide/define-a-garden.html +++ b/docs/user-guide/define-a-garden.html @@ -5,7 +5,7 @@ Define a Garden | Geo Garden Club - + diff --git a/docs/user-guide/downloading.html b/docs/user-guide/downloading.html index fad0e047..f1a8dc0f 100644 --- a/docs/user-guide/downloading.html +++ b/docs/user-guide/downloading.html @@ -5,7 +5,7 @@ Downloading | Geo Garden Club - + diff --git a/docs/user-guide/explore-a-chapter.html b/docs/user-guide/explore-a-chapter.html index 696b4289..c5e651a2 100644 --- a/docs/user-guide/explore-a-chapter.html +++ b/docs/user-guide/explore-a-chapter.html @@ -5,7 +5,7 @@ Explore a Chapter | Geo Garden Club - + diff --git a/docs/user-guide/explore-a-garden.html b/docs/user-guide/explore-a-garden.html index 6f464d58..4726f649 100644 --- a/docs/user-guide/explore-a-garden.html +++ b/docs/user-guide/explore-a-garden.html @@ -5,7 +5,7 @@ Explore a Garden | Geo Garden Club - + diff --git a/docs/user-guide/geobot.html b/docs/user-guide/geobot.html index 5291c3d3..65979d39 100644 --- a/docs/user-guide/geobot.html +++ b/docs/user-guide/geobot.html @@ -5,7 +5,7 @@ GeoBot | Geo Garden Club - + diff --git a/docs/user-guide/guided-tour.html b/docs/user-guide/guided-tour.html index 8073fde4..f2b0f031 100644 --- a/docs/user-guide/guided-tour.html +++ b/docs/user-guide/guided-tour.html @@ -5,7 +5,7 @@ Frequently Asked (Gardening) Questions | Geo Garden Club - + diff --git a/docs/user-guide/observations.html b/docs/user-guide/observations.html index 07b46d2f..c61dab49 100644 --- a/docs/user-guide/observations.html +++ b/docs/user-guide/observations.html @@ -5,7 +5,7 @@ Observations | Geo Garden Club - + diff --git a/docs/user-guide/outcomes.html b/docs/user-guide/outcomes.html index 5f8ba653..b97631e5 100644 --- a/docs/user-guide/outcomes.html +++ b/docs/user-guide/outcomes.html @@ -5,7 +5,7 @@ outcomes | Geo Garden Club - + diff --git a/docs/user-guide/overview.html b/docs/user-guide/overview.html index 7f74334d..846b003c 100644 --- a/docs/user-guide/overview.html +++ b/docs/user-guide/overview.html @@ -5,7 +5,7 @@ Overview | Geo Garden Club - + diff --git a/docs/user-guide/privacy.html b/docs/user-guide/privacy.html index 747f4d00..4a0d7c8d 100644 --- a/docs/user-guide/privacy.html +++ b/docs/user-guide/privacy.html @@ -5,7 +5,7 @@ Privacy Policy | Geo Garden Club - + diff --git a/docs/user-guide/registration.html b/docs/user-guide/registration.html index 9d91eaf1..f33e5d99 100644 --- a/docs/user-guide/registration.html +++ b/docs/user-guide/registration.html @@ -5,7 +5,7 @@ Registration | Geo Garden Club - + diff --git a/docs/user-guide/scenarios.html b/docs/user-guide/scenarios.html index 720e0ccc..08b8f4a9 100644 --- a/docs/user-guide/scenarios.html +++ b/docs/user-guide/scenarios.html @@ -5,7 +5,7 @@ Planting Scenarios | Geo Garden Club - + diff --git a/docs/user-guide/seeds.html b/docs/user-guide/seeds.html index 59942ceb..3b5c4483 100644 --- a/docs/user-guide/seeds.html +++ b/docs/user-guide/seeds.html @@ -5,7 +5,7 @@ Seeds | Geo Garden Club - + diff --git a/docs/user-guide/tasks.html b/docs/user-guide/tasks.html index c49d451f..c3de5a30 100644 --- a/docs/user-guide/tasks.html +++ b/docs/user-guide/tasks.html @@ -5,7 +5,7 @@ Tasks | Geo Garden Club - + diff --git a/docs/user-guide/terms-and-conditions.html b/docs/user-guide/terms-and-conditions.html index 1e5fb5ea..97ab1a76 100644 --- a/docs/user-guide/terms-and-conditions.html +++ b/docs/user-guide/terms-and-conditions.html @@ -5,7 +5,7 @@ Terms and Conditions | Geo Garden Club - + diff --git a/index.html b/index.html index e10b86a3..938c0d2e 100644 --- a/index.html +++ b/index.html @@ -5,7 +5,7 @@ Geo Garden Club | Geo Garden Club - + diff --git a/markdown-page.html b/markdown-page.html index 2c2d5e4b..bb337a9b 100644 --- a/markdown-page.html +++ b/markdown-page.html @@ -5,7 +5,7 @@ Markdown page example | Geo Garden Club - +