[{"id":3645,"title":"Shipping Containers: How We Built an Easy to Use Jenkins Pipeline for ECR","permalink":"https:\/\/eng.wealthfront.com\/2025\/10\/08\/shipping-containers-how-we-built-an-easy-to-use-jenkins-pipeline-for-ecr\/","content":"<!-- wp:paragraph -->\n<p>The DevOps team at Wealthfront has been in the process of migrating services to run inside containers. As part of this process we need a way to build containers using our CI\/CD tool, Jenkins. While we could write a Jenkinsfile for each container image we wanted to build, we identified this was a good opportunity to codify and normalize our container build pipelines so that every team at Wealthfront can leverage the effort we apply to creating a useful container build pipeline.&nbsp;<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Until now, we haven't had a significant need for a container build pipeline because the vast majority of our pipelines have been relatively homogeneous: Java, Chef cookbooks, and a smattering of custom Jenkinsfiles. Now that we're focusing on shipping more containers we need to create a similarly consistent pipeline for container releases.&nbsp;<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>In this post we will walk through why we want a centralized pipeline, design decisions for the pipeline, as well as provide an example of how we test the pipeline code. Finally, we hope to inspire you to codify your own build and release processes with a view for fast, easily-configurable pipelines.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading -->\n<h2 class=\"wp-block-heading\">Why Centralize the Pipeline Configuration<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Before starting a project, it's important to identify the benefits and tradeoffs of a solution. A little forethought now can ensure we're building the right solution at the right time in the right way. In that light, what are the benefits we hope to glean from a centralized container build pipeline?<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:list -->\n<ul class=\"wp-block-list\"><!-- wp:list-item -->\n<li><strong>The ability to enforce a consistent naming scheme for container repositories.<\/strong> If we leave the name open, there is a risk two teams will decide they both want to name their repository \"web-server\".<\/li>\n<!-- \/wp:list-item -->\n\n<!-- wp:list-item -->\n<li><strong>The ability to centrally manage ECR authentication.<\/strong> Individual teams won't need to figure out how to inject the correct credentials to connect with ECR if we can handle it for them.<\/li>\n<!-- \/wp:list-item -->\n\n<!-- wp:list-item -->\n<li><strong>The ability to standardize how ECR repos are configured.<\/strong> For example, ensuring that ECR repos are given the correct AWS tags. This has the added benefit that we can obviate the possibility of typos in tag fields.<\/li>\n<!-- \/wp:list-item -->\n\n<!-- wp:list-item -->\n<li><strong>Reduce the need for specialized knowledge to spin up a new container project.<\/strong> By abstracting the container build and push process we can flatten the learning curve to ship a new container project.<\/li>\n<!-- \/wp:list-item -->\n\n<!-- wp:list-item -->\n<li><strong>Multiply the effect of pipeline work.<\/strong> The effort we put into this one library can be leveraged by future projects when they need to ship containers.<\/li>\n<!-- \/wp:list-item -->\n\n<!-- wp:list-item -->\n<li>Finally, a centralized pipeline <strong>should set us up for future work<\/strong>: we want to easily add features that benefit all image build pipelines without needing to touch each of the pipelines.<\/li>\n<!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>That's a pretty compelling list of benefits! Before we move on, though, let's think through what downsides\u2013if any\u2013might exist:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:list -->\n<ul class=\"wp-block-list\"><!-- wp:list-item -->\n<li>A centralized pipeline means that <strong>introducing a bug affects <\/strong><strong><em>every<\/em><\/strong><strong> image build pipeline<\/strong>.<\/li>\n<!-- \/wp:list-item -->\n\n<!-- wp:list-item -->\n<li>Abstracting away the complexity of image build commands <strong>deprives engineers of the opportunity to learn the build commands<\/strong> and can inadvertently build knowledge silos. A motivated engineer can learn the image build commands but we won't be giving them a natural on-ramp to learn them.<\/li>\n<!-- \/wp:list-item -->\n\n<!-- wp:list-item -->\n<li><strong>The pipeline doesn't run locally<\/strong>, so if someone is trying to build the image locally, they will need to write their own build commands rather than leverage the work we put into the pipeline. This also further compounds the missed learning opportunities.<\/li>\n<!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>As we think about these tradeoffs, the amount of flexibility and consistency we can achieve through centralized pipelines easily outweighs the downsides. In addition, if we write guides for engineers walking through the happy path of building a container on their local machine, we can help reduce some of the downsides.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>This seems like a great idea, so let's run with it!<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading -->\n<h2 class=\"wp-block-heading\">Writing a Pipeline Helper Using the Builder Pattern<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>One way to achieve a shared pipeline is to have a pipeline library which parses default values from a config struct. Below is an overly-simplified example:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code {\"language\":\"groovy\",\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>\/\/ jenkinsLibraries\/vars\/dockerReleasePipelineHardToTest.groovy\n\ndef call(Map config = [:]) {\n  def target = (config.remove('target') ?: 'release') as String\n  def owner = config.remove('owner') as String\n  if (owner == null || owner.isEmpty()) {\n    throw new Exception('owner must be defined')\n  }\n\n  pipeline {\n    agent {\n      label 'dockerce'\n    }\n\n    stages {\n      stage('Build') {\n        steps {\n          sh \"\"\"\n              aws ecr create-repository --region=us-west-2 --repository-name=example --tags 'Key=owner,Value=${owner}'\"\n              docker buildx build --target='${target}' --tag example:latest .\n              docker push example:latest\n           \"\"\"\n        }\n      }\n    }\n  }\n}\n<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>While it is possible to test Jenkinsfiles like this using<a href=\"https:\/\/github.com\/jenkinsci\/JenkinsPipelineUnit\"> JenkinsPipelineUnit<\/a>, as we add complexity the JenkinsPipelineUnit tests would quickly balloon in complexity.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Instead, we can use the builder pattern to handle most of the work of our pipeline logic. We see several advantages of using a builder rather than putting all the logic in the pipeline:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:list -->\n<ul class=\"wp-block-list\"><!-- wp:list-item -->\n<li>It's easy to define <strong>default values<\/strong><\/li>\n<!-- \/wp:list-item -->\n\n<!-- wp:list-item -->\n<li><strong>Discoverability<\/strong> of available options is easy<\/li>\n<!-- \/wp:list-item -->\n\n<!-- wp:list-item -->\n<li>We can use the class to <strong>generate commands <\/strong>for our pipeline<\/li>\n<!-- \/wp:list-item -->\n\n<!-- wp:list-item -->\n<li>It's <strong>simple to test<\/strong> the class as well as the generated commands<\/li>\n<!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Here is the most basic example of the builder pattern in groovy:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code {\"language\":\"groovy\",\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>\/\/ jenkinsLibraries\/src\/com\/wealthfront\/jenkins\/DockerReleaseTarget.groovy\n\npackage com.wealthfront.jenkins\n\nclass DockerReleaseTarget {\n  String target\n  String owner\n\n  private String region = 'us-west-2'\n  private String awsAccount = '1234567890'\n\n  DockerReleaseTarget() {\n    this.target = 'release'\n    this.name = ''\n  }\n\n  DockerReleaseTarget setName(String name) {\n    this.name = name\n    return this\n  }\n\n  DockerReleaseTarget withTarget(String s) {\n    this.target = s\n    return this\n  }\n\n  DockerReleaseTarget withOwner(String s) {\n    def allowedOwners = [ 'devops', 'trading', 'etc....' ]\n    if (!s) {\n      throw new IllegalStateException('Owner must be non-empty.')\n    }\n    if (!allowedOwners.contains(s)) {\n      throw new IllegalStateException(\"Invalid owner: '${s}'. Must be one of: ${allowedOwners.join(', ')}\")\n    }\n    this.owner = s\n    return this\n  }\n\n  void runValidation() {\n    if (!this.owner?.trim()) {\n      throw new IllegalStateException(\"The 'owner' field must not be empty, use withOwner(owner).\")\n    }\n    if (!this.target.trim()) {\n      throw new IllegalStateException('The target must not be empty, use withTarget(\"targetName\")')\n    }\n    if (!this.name.trim()) {\n      throw new IllegalStateException('The name must not be empty, this is an issue with the dockerReleasePipeline.')\n    }\n  }\n\n  String getEcrRepo() {\n    return \"${this.awsAccount}.dkr.ecr.${this.region}.amazonaws.com\/${this.name}\"\n  }\n\n  String ecrRepoLoginScript() {\n    return \"aws ecr get-login-password --region ${this.region} | docker login --username AWS --password-stdin ${this.getEcrRepo()}\"\n  }\n\n  String createEcrScript() {\n    return \"aws ecr create-repository --region=${this.region} --repository-name=${this.name} --tags 'Key=owner,Value=${this.owner}'\"\n  }\n\n  String dockerBuildScript() {\n    return \"docker buildx build --target='${this.target}' ${this.name} .\"\n  }\n\n  String dockerPushScript() {\n    return \"docker push ${this.name}\"\n  }\n\n}\n<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>Notice how this allows us to set standard values for the AWS Account and Region. At the same time, we define a default value for the owner tag, but allow users to override the tag with a list of allowed options. Finally, we are generating the relevant docker commands in this class so we can test that the commands will work as expected.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Of course, we have many more configuration options available so that engineers can pick target platforms, naming suffixes, dockerfile names, and more. This wide variety of options is important to let us support the scope of config the various teams using these pipelines require.&nbsp;<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code {\"language\":\"groovy\",\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>addTag(String)\nwithShellTag(String)\nwithTags(String... tagList)\nwithPush(Boolean shouldPush = true)\nwithRepoSuffix(String s)\nwithTarget(String s)\nwithParallel(Boolean p)\nwithOwner(String s)\nwithBuildArg(String... args)\nwithRawBuildArg(String... args)\nwithArchitectures(String... archs)\nwithDockerfile(String dockerfile)\nwithArchitectureSpecificDockerfile(String arch, String dockerfile)\nwithLatestTag(Boolean tagLatest = true)\nasPrivateRepo(Boolean asPrivate = false)<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>Looking at all these options, consider how important it is that we test how the options combine. With that in mind, let\u2019s look at how we can test this library.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading -->\n<h2 class=\"wp-block-heading\">Testing<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Wealthfront values testing, which is a topic <a href=\"https:\/\/eng.wealthfront.com\/tag\/testing\/\">we've written about many times before<\/a>. Our Jenkins libraries are no exceptions. Below is an example of how we can test the withOwner function to ensure it behaves as expected:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code {\"language\":\"groovy\",\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>\/\/ jenkinsLibraries\/srctest\/com\/wealthfront\/jenkins\/DockerReleaseTargetTest.groovy\n\npackage com.wealthfront.jenkins\n\nimport com.lesfurets.jenkins.unit.BasePipelineTest\nimport org.junit.Test\n\nclass DockerReleaseTargetTest extends BasePipelineTest {\n  @Test\n  void testDockerReleaseTarget_withOwner() {\n    def script = loadScript('vars\/helper.groovy')\n    def drt = script.newDockerReleaseTarget()\n                      .withOwner('devops')\n\n    assert drt.createEcrScript().contains('Key=owner,Value=devops')\n  }\n\n  @Test\n  void testDockerReleaseTarget_invalidOwnerShouldFail() {\n    def script = loadScript('vars\/helper.groovy')\n    try {\n      def drt = script.newDockerReleaseTarget()\n                        .withOwner('invalid-owner-name')\n      fail 'Expected exception was not thrown'\n    } catch (IllegalStateException x) {\n    }\n  }\n}\n<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>This testing pattern allows us to confirm that validations work as expected and that the docker commands are generated as expected.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>These two example tests show that the owner value is propagated correctly to the createECRScript as well as ensuring that invalid owners will throw an exception.&nbsp;<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading -->\n<h2 class=\"wp-block-heading\">Implementation in a Pipeline<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Because we're using our tested library for generating our commands, the final example pipeline becomes much simpler:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code {\"language\":\"groovy\",\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>\/\/ jenkinsLibraries\/vars\/dockerReleasePipeline.groovy\n\ndef call(Map config = [:]) {\n  def drt = config.remove('releaseTarget')\n  if (drt == null) {\n    throw new Exception('releaseTarget must be defined')\n  }\n\n  drt\n    .setName(env.JOB_NAME)\n    .runValidation()\n\n  pipeline {\n    agent {\n      label 'dockerce'\n    }\n\n    stages {\n      stage('Build') {\n        steps {\n          withCredentials([\n            usernamePassword(credentialsId: 'ecr-aws-creds',\n            usernameVariable: 'AWS_ACCESS_KEY_ID',\n            passwordVariable: 'AWS_SECRET_ACCESS_KEY'),\n          ]) {\n            sh drt.ecrRepoLoginScript()\n          }\n          sh drt.createEcrScript()\n          sh drt.dockerBuildScript()\n          sh drt.dockerPushScript()\n        }\n      }\n    }\n  }\n}\n<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:paragraph -->\n<p>Notice how we can force the repo name using the .setName(env.JOB_NAME) function in the final pipeline. In addition, we call .runValidation() as a final check of the config before we start executing on the runner.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Because the validation occurs before we claim an agent, if there is a config error, the pipeline will fail right away. This gives immediate feedback to the engineer what parts they need to fix.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>When users want build a container image, they can now use the pipeline template and pass in a builder:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code {\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>@Library(\"JenkinsFiles\") _\n\ndockerImageBuildPipeline(\n  releaseTarget: helper.newDockerReleaseTarget()\n                        .withOwner('devops')\n                        .withTarget('release-debug')\n)\n<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:heading -->\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>And that's it! While this blog post has presented the most basic implementation of a shared container build pipeline, hopefully you can see how this lays a foundation that sets us up to achieve our original goals of centralizing configuration, abstracting away ECR auth, standardizing repo tags, and setting us up to add new features down the road.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Overall this pattern of centralizing build pipelines, parameterizing them using the builder pattern, and testing the pipeline code leads to a pleasant engineering experience both when writing and using the pipeline. Discoverability of features is high and iteration cycles are tight because bad config pushes fail quickly.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>If you want to join us in driving the future of containers at Wealthfront\u2014prioritizing engineering experience, testing, and automation\u2014consider<a href=\"https:\/\/www.wealthfront.com\/careers\"> joining our engineering team<\/a>!&nbsp;<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:separator {\"backgroundColor\":\"cyan-bluish-gray\"} -->\n<hr class=\"wp-block-separator has-text-color has-cyan-bluish-gray-color has-alpha-channel-opacity has-cyan-bluish-gray-background-color has-background\"\/>\n<!-- \/wp:separator -->\n\n<!-- wp:heading {\"level\":4} -->\n<h4 class=\"wp-block-heading\">Disclosures<\/h4>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph {\"fontSize\":\"small\"} -->\n<p class=\"has-small-font-size\">The information contained in this communication is provided for general informational purposes only, and should not be construed as investment or tax advice. Nothing in this communication should be construed as a solicitation or offer, or recommendation, to buy or sell any security. Any links provided to other server sites are offered as a matter of convenience and are not intended to imply that Wealthfront Corporation or its affiliates endorses, sponsors, promotes and\/or is affiliated with the owners of or participants in those sites, or endorses any information contained on those sites, unless expressly stated otherwise.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph {\"fontSize\":\"small\"} -->\n<p class=\"has-small-font-size\">Investment advisory services are provided by Wealthfront Advisers LLC, an SEC-registered investment adviser. Brokerage services are provided by Wealthfront Brokerage LLC, Member <a href=\"https:\/\/www.finra.org\/\">FINRA<\/a>\/<a href=\"https:\/\/www.sipc.org\/\">SIPC<\/a>. Financial planning tools are provided by Wealthfront Software LLC.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph {\"fontSize\":\"small\"} -->\n<p class=\"has-small-font-size\">All investing involves risk, including the possible loss of money you invest, and past performance does not guarantee success. Please see our <a href=\"https:\/\/www.wealthfront.com\/legal\/disclosure\">Full Disclosure<\/a> for important details.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph {\"fontSize\":\"small\"} -->\n<p class=\"has-small-font-size\">Wealthfront Advisers LLC, Wealthfront Brokerage LLC, and Wealthfront Software LLC are wholly owned subsidiaries of Wealthfront Corporation.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph {\"fontSize\":\"small\"} -->\n<p class=\"has-small-font-size\">\u00a9 2025 Wealthfront Corporation. All rights reserved.<\/p>\n<!-- \/wp:paragraph -->","excerpt":"The DevOps team at Wealthfront has been in the process of migrating services to run inside containers. As part of this process we need a way to build containers using our CI\/CD tool, Jenkins. While we could write a Jenkinsfile for each container image we wanted to build, we identified this was a good opportunity... <a class=\"moretag\" href=\"https:\/\/eng.wealthfront.com\/2025\/10\/08\/shipping-containers-how-we-built-an-easy-to-use-jenkins-pipeline-for-ecr\/\" aria-label=\"Read more about Shipping Containers: How We Built an Easy to Use Jenkins Pipeline for ECR\">Read more<\/a>","date":"2025-10-08 12:15:22","author":"Oliver Isaac","categories":["wealthfront engineering"],"tags":["automation","ci","continuous-integration","jenkins","testing"]},{"id":2772,"title":"Building Wealthfront&#8217;s multi-platform design system","permalink":"https:\/\/eng.wealthfront.com\/2022\/05\/10\/building-wealthfronts-multi-platform-design-system\/","content":"<!-- wp:paragraph -->\n<p>Over the past two years at Wealthfront we\u2019ve been building a design system from the ground up across all our frontend platforms: Android, iOS, and web. If you\u2019re a Wealthfront client you may have noticed sweeping, iterative visual changes across our apps as we introduced new components and migrated features to adopt these components.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>At this point, the core design system components have been implemented on all platforms, so we wanted to share a bit about our journey building a multi-platform design system.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading -->\n<h2>Motivation<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>In the past, design patterns, reusable components, and other shared utilities grew organically on all platforms. If this was already happening, why create a design system in the first place?<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>An organic, bottoms-up approach led by individual designers and engineers can work well for small, nimble teams but begins to break down as teams scale.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>First, there is no guarantee that components are always being built with reusability in mind, nor that they solve all the necessary product use cases. This can lead to code duplication and extra effort which can result in an overall lower development velocity and an inconsistent user experience.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Furthermore, when reusable components are built, there is no guarantee that the equivalent components are built on other platforms. For example, if iOS builds a <code>Button<\/code> component but Android and web don\u2019t have this same component, it can introduce more confusion and inconsistency for engineers and designers.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>If these reusable components are built on all platforms, there is no guarantee that designers are aware, nor that an equivalent component exists in their design tool: Figma. This can lead to churn redesigning an existing component, which in turn can lead to further code duplication and a fractured user experience.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Finally, even if all platforms have implemented a component and designers are aware of each platform\u2019s individual capabilities, there is still no guarantee that the components are implemented with the same look and feel on all platforms. This too can lead to confusion, asymmetrical effort spent on different platforms depending on the state of the component, and an inconsistent user experience across platforms.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>In the face of these growing challenges, we decided it was the right time to begin a concerted design system effort at Wealthfront.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading -->\n<h2>The design vision<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Before immediately jumping into building the design system it was important to set the stage. The design team explored various future states and an overall design direction for the product. This initial exploration directly inspired many of the components that are currently being used in our design system and the current state of the product.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:image {\"id\":2794,\"sizeSlug\":\"large\",\"linkDestination\":\"none\"} -->\n<figure class=\"wp-block-image size-large\"><img src=\"https:\/\/eng.wealthfront.com\/wp-content\/uploads\/2022\/05\/Design-system-vision-1024x688.png\" alt=\"screenshot examples of the existing Wealthfront app, the north star vision, and the final implementation\" class=\"wp-image-2794\"\/><\/figure>\n<!-- \/wp:image -->\n\n<!-- wp:paragraph -->\n<p>The design vision solidified the north star for the initial phase of building the design system. It also helped gather support and excitement from engineers, designers, product, and leadership.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Usually, coupling two large initiatives \u2013 the initial phase of the design system and a product-wide refresh \u2013 can be a recipe for disaster. However, in this scenario, it actually helped to showcase design system progress and encourage adoption of components. If the design system standardized on the existing product it would be hard to differentiate what is and is not using the design system. It also quickly demonstrated the benefits of a design system to iteratively uplevel the entire product instead of a single feature.&nbsp;<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>With this design vision in mind, the next step was to define the design system team\u2019s mission to determine how to prioritize this and future work.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading -->\n<h2>The design system mission<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>The team vision has evolved since initially setting out, but the core tenets remain the same:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><em>\u201cThe Design System team\u2019s mission is to raise the quality bar of our products while improving consistency for our clients and productivity of both engineers &amp; designers. We do this by building tools, frameworks, and processes that empower our product teams to deliver more cohesive experiences and make our products easier to maintain.\u201c<\/em><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>This mission guides prioritization and decision making within the design system team. For example, which components to work on before others, which frameworks would be most beneficial, or how to improve processes.&nbsp;<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>With a clear vision and mission, it was time to execute.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading -->\n<h2>Building a multi-platform design system<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>In the past, there were often significant visual or functional product feature differences between the platforms. In addition to unifying features across a platform, an initial motivation for the design system was to unify features across platforms. Providing the same fundamental building blocks \u2013 design system components \u2013 on all platforms makes this possible.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Therefore, we aimed to build a <a href=\"https:\/\/dbanks.design\/blog\/multi-platform\/\">multi-platform, cohesive design system<\/a>. When creating new components or frameworks we start from a single point, and then consider modifications that respect each platform\u2019s standards when appropriate. This helps ensure a unified experience across platforms, as opposed to starting from multiple points and working backwards. It provides a predictable experience for clients (even when switching between platforms), and allows designers to design once, instead of two or three times. While not entirely accurate, you might say this is a \u201cdesign once, build anywhere\u201d approach.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>For example, the <code>Dialog<\/code> on web floats in the center of the screen on desktop, but its counterpart <code>BottomSheet<\/code> on mobile is attached to the sides and bottom of the screen.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:image {\"id\":2795,\"sizeSlug\":\"large\",\"linkDestination\":\"none\"} -->\n<figure class=\"wp-block-image size-large\"><img src=\"https:\/\/eng.wealthfront.com\/wp-content\/uploads\/2022\/05\/design-system-dialog-1024x511.png\" alt=\"screenshot showing the visual cohesion of the dialog on desktop web and the bottom sheet on mobile\" class=\"wp-image-2795\"\/><\/figure>\n<!-- \/wp:image -->\n\n<!-- wp:paragraph -->\n<p>They are still cohesive since they share the same <a href=\"https:\/\/spectrum.adobe.com\/page\/design-tokens\/\">design tokens<\/a>: padding, border radius, colors, and typography. However, the exact positioning respects each platform\u2019s existing standards to match user expectations.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>In the same way we aim to create cohesive components across platforms, we aim to do the same for each component\u2019s API across platforms.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":3} -->\n<h3>Multi-platform API design<\/h3>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>One of the most important parts of a successful multi-platform design system component library is a cohesive API on all platforms, ideally identical. This has two primary motivations.&nbsp;<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>First, it guarantees all components have similar options which means it can support the same functionalities and will likely have similar edge cases. The chances that something else is visually or functionally different increases if the API is different across platforms.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>A second but equally important motivation is to create a standardized vocabulary for all designers and engineers. For example, when a designer describes \u201c<em>a callout card with a small heading, body text, and primary button with large spacing between<\/em>\u201d, other designers and engineers should be able to easily translate this into code on their respective platform. Below is an example of how this might be translated into design system components on web and how similar code would be rendered on each platform.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:code {\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>&lt;Card variant=\"callout\"&gt;\n  &lt;Stack spacing=\"large\"&gt;\n    &lt;Heading variant=\"small\" as=\"h2\"&gt;\n      This is a Card\n    &lt;\/Heading&gt;\n    &lt;Text&gt;Cards are ways...&lt;\/Text&gt;\n    &lt;Button variant=\"primary\" as=\"button\"&gt;\n      Got it\n    &lt;\/Button&gt;\n  &lt;\/Stack&gt;\n&lt;\/Card&gt;<\/code><\/pre>\n<!-- \/wp:code -->\n\n<!-- wp:image {\"id\":2796,\"sizeSlug\":\"large\",\"linkDestination\":\"none\"} -->\n<figure class=\"wp-block-image size-large\"><img src=\"https:\/\/eng.wealthfront.com\/wp-content\/uploads\/2022\/05\/design-system-card-1024x687.png\" alt=\"example of how the card code snippet is rendered on Android, iOS, and web\" class=\"wp-image-2796\"\/><\/figure>\n<!-- \/wp:image -->\n\n<!-- wp:paragraph -->\n<p>Android and iOS have equivalent component counterparts that can be used to construct the same UI.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>The exact process we follow for drafting these APIs is shared further below. Before that, let\u2019s discuss the advantages and disadvantages of building a multi-platform design system.&nbsp;<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":3} -->\n<h3>Advantages<\/h3>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Arguably the biggest benefit of multi-platform design systems is that it requires taking a step back from platform specifics and thinking through components from first principles. If building a web-only design system, it can be easy to never question web standards or specifications. However, in order to build an effective multi-platform design system, you <em>must <\/em>do so.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>For example, a <code>Button<\/code> and <code>Link<\/code> can be popular components on web that map to native <code>button<\/code> and <code>a<\/code> (anchor) elements. From a design perspective these can often be treated as conceptually equivalent: a user clicks something and it does something. Implementing this only on web may result in two components with duplicated styling. However, when also considering Android and iOS which don\u2019t make this same distinction, it raises the question if two distinct components on web are necessary.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>As a result of taking a step back and considering all platforms, we instead have a single <code>Button<\/code> component on web that supports rendering as either an anchor or button element for the important semantic differentiation. This allows designers to conceptually think about one component, without needing to consider whether they are linking to another webpage or performing an action on web.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Another benefit of building a multi-platform design system is sharing platform or framework patterns across platforms. For example, the flexibility and composition that React provides encourages a certain way of thinking about components that can inspire API patterns which impacts the component on all platforms. Longer term, we\u2019re excited about Jetpack Compose and SwiftUI which have more in common with React, potentially leading to even further component cohesion across all platforms.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>While the platform differences can provide plenty of advantages, they bring with them their own disadvantages.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":3} -->\n<h3>Disadvantages<\/h3>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Each platform has differences that can arise in various situations and impact visuals, functionality, or API design. Some of the more common differences include:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:list -->\n<ul><li><em><strong>Differing standards<\/strong>:<\/em> for example, each platform has slightly different specifications and exceptions for tap area targets. When designing smaller components that get close to these minimum sizes we need to reconcile these differences. The simplest is to pick the largest minimum since this guarantees the others are met as well. Accessibility is only one aspect; every platform has varying standards across the spectrum.<\/li><li><em><strong>Differing languages<\/strong>:<\/em> Kotlin on Android, Swift on iOS, and TypeScript on web. They each provide some level of type safety which is always a benefit when consuming or refactoring components in a design system. However, the differences in the type systems can introduce their own API design challenges. For example, on Android we avoid XML for complex APIs due to limited type safety. Instead, the API is written in Kotlin to take advantage of powerful language-level constructs like sealed types. On web with React and TypeScript, it\u2019s not possible to restrict child components to only certain types. As a result, the type definitions are more permissive than what is allowed in practice so some of these constraints are runtime checks which run only in development builds.<\/li><li><em><strong>Differing frameworks and paradigms<\/strong>:<\/em> while having different frameworks or paradigms can be an advantage to provide multiple perspectives and provide inspiration, it also requires reconciling these differences. For example, React and TypeScript are less strict so it\u2019s easy to provide flexible APIs. This is convenient for APIs that need to be flexible, but becomes a challenge for APIs that need to be more restrictive.<\/li><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>At the end of the day, while building a multi-platform design system may require additional effort, it does result in an overall better design system.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading -->\n<h2>Component creation process<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>One of the first and most important processes to define early on was for component creation. We experimented with a few approaches, but settled on the process we\u2019re using today.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>The first decision is where something should live. Should it be a new design system component? Should it be added to an existing design system component? Should it be a feature-specific custom component?<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>The following flow chart is a high-level summary of how we approach thinking about adding new components to the design system.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:image {\"id\":2797,\"sizeSlug\":\"large\",\"linkDestination\":\"none\"} -->\n<figure class=\"wp-block-image size-large\"><img src=\"https:\/\/eng.wealthfront.com\/wp-content\/uploads\/2022\/05\/design-system-flow-1024x681.png\" alt=\"flowchart of how we decide when to make a new component or update an existing component\" class=\"wp-image-2797\"\/><\/figure>\n<!-- \/wp:image -->\n\n<!-- wp:paragraph -->\n<p>Once a decision has been made to add or update a design system component, the next step is a design-focused component sheet proposal.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":3} -->\n<h3>Design component sheet proposal<\/h3>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>The component sheet proposal is a design-led exercise to explicitly list the specifications, or design tokens, to use for typography, spacing, and colors. It also includes an exhaustive exploration of use cases and the variants those different use cases introduce.&nbsp;<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:image {\"id\":2798,\"sizeSlug\":\"full\",\"linkDestination\":\"none\"} -->\n<figure class=\"wp-block-image size-full\"><img src=\"https:\/\/eng.wealthfront.com\/wp-content\/uploads\/2022\/05\/design-system-component-proposal.png\" alt=\"example design component sheet for the summary list component\" class=\"wp-image-2798\"\/><\/figure>\n<!-- \/wp:image -->\n\n<!-- wp:image {\"id\":2799,\"sizeSlug\":\"large\",\"linkDestination\":\"none\"} -->\n<figure class=\"wp-block-image size-large\"><img src=\"https:\/\/eng.wealthfront.com\/wp-content\/uploads\/2022\/05\/design-system-component-example-1024x537.png\" alt=\"exploration on the design component sheet for the summary list component\" class=\"wp-image-2799\"\/><\/figure>\n<!-- \/wp:image -->\n\n<!-- wp:paragraph -->\n<p>The specifications expedite implementation and ensure all platforms are using the same design tokens, which results in a cohesive experience across platforms and across the design system. The variants define exactly which options will need to be supported which ties directly into the next step.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":3} -->\n<h3>Engineering component API proposal<\/h3>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>This step is an engineering-led exercise to explicitly define the public API based on all the variants defined in the design component sheet.&nbsp;<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Naming is one of the critical parts of this step. We survey existing design systems, existing platform specifications, and anywhere else we can draw inspiration. There isn\u2019t a single rule, but we usually follow these guidelines:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:list -->\n<ul><li>If there is an existing, widely accepted component name that maps well on all platforms, it will be used. For example, a <code>Card<\/code> component.&nbsp;<\/li><li>If there is not an existing name that maps well on all platforms, we intentionally avoid any existing platform-specific names to avoid confusion. For example, the <code>SummaryList<\/code> component is similar to the <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTML\/Element\/dl\">description list element on web<\/a>, so we chose not to use that name. This avoids confusion by overloading this meaning on web and by bringing web specifications to mobile platforms. Instead, <code>SummaryList<\/code> provides a similar meaning without implying any existing definitions.<\/li><li>If there are existing but unique names per platform that also align with the component and platform differences, they will be used. Some examples include the <code>Dialog<\/code> on web and the <code>BottomSheet<\/code> on Android and iOS.<\/li><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>This naming strategy also applies to all the options and values for each component, although that\u2019s typically easier. Without this process, it wouldn\u2019t be possible to have the shared vocabulary mentioned earlier.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>The other critical part of this step is the actual API and options. Typically, we brainstorm several API proposals that approach the component from different perspectives. Some are more general, whereas others are more specific. Exploring multiple APIs and perspectives can uncover interesting insights such as additional use cases or unexpected connections to other components.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>There are a few principles we keep in mind when designing component APIs:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:list -->\n<ul><li><em><strong>Consistency<\/strong>:<\/em> for example, when there is a generic slot for any component, we use the term \u201ccontent.\u201d This can show up in component options as <code>primaryContent<\/code>, <code>secondaryContent<\/code>, <code>helperContent<\/code>, or <code>trailingContent<\/code>. This cross-component consistency can help imply how a component\u2019s option should be used before reading any documentation or relying on types.<\/li><li><em><strong>Simplicity<\/strong>:<\/em> ideally, the API is simple. This means there are minimal options, <a href=\"https:\/\/kentcdodds.com\/blog\/make-impossible-states-impossible\">impossible states are impossible<\/a>, and the API is self explanatory. Typically, this comes down to iteration. Simplicity takes time.<\/li><li><em><strong>Flexible versus opinionated<\/strong>:<\/em> the design requirement should be reflected in the API. If the design has loose requirements or treats things as containers, a generic slot that accepts arbitrary content is a flexible pattern. It <a href=\"https:\/\/kentcdodds.com\/blog\/inversion-of-control\">inverts the control<\/a> of what is rendered to the consumer of the component. On the other hand, if the design has strict requirements, the API should be restricted to only those options. While it may seem obvious, this is important to get right. Providing an API that is too flexible for something intended to be opinionated can result in unexpected uses, maintenance headaches, and frustration for engineers consuming the design system. The opposite is also true: providing an API that is too opinionated that was intended to be flexible leads to the same problems.<\/li><li><em><strong>Accessibility<\/strong>:<\/em> it\u2019s important to intentionally consider accessibility while designing the API. This can surface in many ways, such as the actual naming of options, removing potentially inaccessible functionality, or adding new options specifically for accessibility. For example, on web, a single component can map to multiple HTML elements so we expose an <code>as<\/code> option to control which underlying element is rendered. We do this for both the <code>Button<\/code> and <code>Text<\/code> components.<\/li><li><em><strong>Platform implications<\/strong>:<\/em> for example, avoiding reserved keywords on different platforms. With the <code>SummaryList<\/code>, we considered a <code>key<\/code> option, but this isn\u2019t possible on web since that is one of only two <a href=\"https:\/\/reactjs.org\/warnings\/special-props.html\">special React props<\/a> that can\u2019t be used.<\/li><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>The exact design and engineering process and patterns are constantly evolving, but these are the core themes that are commonly considered or commonly arise.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":3} -->\n<h3>Component prototype<\/h3>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>A component prototype is typically done on at least one platform with the proposed API. It can help uncover unexpected edge cases, challenges with the API, or challenges with the way the component was designed. It\u2019s also a good opportunity to test different interactions or animations that can be challenging in Figma.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>This step is usually done in parallel with the previous engineering API proposal step.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":3} -->\n<h3>Implementation<\/h3>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Once all of the previous steps have been completed and finalized with the team, the last step is implementation.&nbsp;<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>The design component sheet proposal and engineering API proposal provide all of the visual, functional, and technical details to implement the component on all platforms. The implementation step also includes appropriate testing (unit tests, screenshot tests, screen reader tests), documentation (component galleries in the mobile apps, Storybook on web), and migration if necessary.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>There are commonly unknowns that arise during implementation, so we loop back to the appropriate previous step depending on the issue.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>We\u2019ve found this general process balances the overall effort with multi-platform cohesion. Skipping one of these steps can result in inconsistent components, behavior, or API on the different platforms.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading -->\n<h2>Current state of the design system<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Since the inception of the design system, we\u2019ve built many of the core components needed across the app such as the <code>Button<\/code>, <code>Card<\/code>, and <code>TextField<\/code>. There are always new components to build, but we\u2019ve recently been spending more time outside of only building components.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>The blog post <a href=\"https:\/\/www.designsystemsforfigma.com\/blog\/creating-components-for-mature-design-systems\"><em>Creating Components For Mature Design Systems<\/em><\/a> provides the following categorization for different design system stages.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:image {\"id\":2800,\"sizeSlug\":\"large\",\"linkDestination\":\"none\"} -->\n<figure class=\"wp-block-image size-large\"><img src=\"https:\/\/eng.wealthfront.com\/wp-content\/uploads\/2022\/05\/design-system-stages-1024x250.png\" alt=\"the different stages of design system\" class=\"wp-image-2800\"\/><\/figure>\n<!-- \/wp:image -->\n\n<!-- wp:paragraph -->\n<p>According to this framework, we\u2019re probably somewhere between an early stage system and a medium stage system. This means we spend less time building components and more time gathering context, (re)defining, and auditing. Recently, we have spent more of our time building the broader design system ecosystem, such as tooling and frameworks.&nbsp;<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading -->\n<h2>Examples<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>That covers a lot of the history and processes, but doesn\u2019t provide any concrete examples. Below are a few examples of design system components and related projects.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":3} -->\n<h3>TextField component<\/h3>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>The <code>TextField<\/code> is one of the most common types of input. It\u2019s fairly standard in that it\u2019s similar to many other text field components in other design systems. For example, it contains a label, optional placeholder, optional leading icon, optional trailing content, optional helper content, and optional error message.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:image {\"align\":\"center\",\"id\":2801,\"sizeSlug\":\"full\",\"linkDestination\":\"none\"} -->\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-full\"><img src=\"https:\/\/eng.wealthfront.com\/wp-content\/uploads\/2022\/05\/design-system-label.gif\" alt=\"text field component morphing between different use cases\" class=\"wp-image-2801\"\/><\/figure><\/div>\n<!-- \/wp:image -->\n\n<!-- wp:paragraph -->\n<p>By default, the <code>TextField<\/code> allows any typed input. At Wealthfront, there are several types of inputs that are common across the product and can benefit from additional formatting to improve the client experience.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:image {\"align\":\"center\",\"id\":2802,\"sizeSlug\":\"large\",\"linkDestination\":\"none\"} -->\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-large\"><img src=\"https:\/\/eng.wealthfront.com\/wp-content\/uploads\/2022\/05\/design-system-text-field-1024x179.png\" alt=\"text field component formatting options\" class=\"wp-image-2802\"\/><\/figure><\/div>\n<!-- \/wp:image -->\n\n<!-- wp:paragraph -->\n<p>As a result, there are also several formatted <code>TextField<\/code> components for telephone input, social security number input, and a dollar amount input. These are all extensions on top of the <code>TextField<\/code> so additional formatted input types can be easily added as needed.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":3} -->\n<h3>SummaryList component<\/h3>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>A less common component seen in other design systems is the <code>SummaryList<\/code> component. It\u2019s used to display a summary of data in a list. This is commonly used to display metadata about an entity.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:image {\"align\":\"center\",\"id\":2803,\"sizeSlug\":\"full\",\"linkDestination\":\"none\"} -->\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-full\"><img src=\"https:\/\/eng.wealthfront.com\/wp-content\/uploads\/2022\/05\/design-system-summary-list.png\" alt=\"summary list one column versus two column example\" class=\"wp-image-2803\"\/><\/figure><\/div>\n<!-- \/wp:image -->\n\n<!-- wp:paragraph -->\n<p>On smaller screens, each item\u2019s key and value are on the same row with the items stacked. However, on larger screens like desktop web this can result in a lot of wasted space. Therefore, this component contains a column option that only applies on larger screens to take advantage of this additional space by rendering the items in more than one column. This is an example of taking platform considerations into account to improve components on a given platform.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":3} -->\n<h3>Color system and theming<\/h3>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>A recent non-component based example of a larger design system project was revamping our color system. Last year, the brand colors were revised and are now widely used on the marketing site. Before now, these colors hadn\u2019t made their way into the design system.&nbsp;<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>The motivation of this project was to introduce these new brand colors as distinct themes. While mobile already supported light and dark mode theming, web did not. Before this project could even begin, we had to deprecate IE 11 support and adopt <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/CSS\/Using_CSS_custom_properties\">CSS Variables<\/a> which enable theming on web. Once all platforms supported theming, the next step was to update those themes.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:image {\"align\":\"center\",\"id\":2804,\"sizeSlug\":\"large\",\"linkDestination\":\"none\"} -->\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-large\"><img src=\"https:\/\/eng.wealthfront.com\/wp-content\/uploads\/2022\/05\/design-system-light-theme-1024x472.png\" alt=\"color system light mode theme\" class=\"wp-image-2804\"\/><\/figure><\/div>\n<!-- \/wp:image -->\n\n<!-- wp:image {\"align\":\"center\",\"id\":2805,\"sizeSlug\":\"large\",\"linkDestination\":\"none\"} -->\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-large\"><img src=\"https:\/\/eng.wealthfront.com\/wp-content\/uploads\/2022\/05\/design-system-dark-theme-1024x472.png\" alt=\"color system dark mode theme\" class=\"wp-image-2805\"\/><\/figure><\/div>\n<!-- \/wp:image -->\n\n<!-- wp:paragraph -->\n<p>Design defined an algorithm that accepted a handful of our brand colors and generated a robust, accessible theme based on those inputs. The algorithm was converted to a script which allows us to generate arbitrary themes based on our brand colors. It includes contrast checking common background and foreground color pairings and output for all platforms: XML for Android, JSON for iOS, and a Stylesheet with CSS Variables for web.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Another big benefit of this new color system is nested theming. With the way the color system was structured and designed, it\u2019s possible to theme subsections of pages or individual components. For example, a theme can be applied directly to the <code>Card<\/code> component to make it visually unique while still being accessible and visually aligned with the rest of the product.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading -->\n<h2>Conclusion<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>While a design system is constantly evolving, we are excited to share the progress we have made so far on this journey. It\u2019s helped improve the consistency throughout the product across platforms, provided a standardized vocabulary, and increased our velocity.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>If working on an evolving design system sounds interesting to you, or you want to help <a href=\"https:\/\/blog.wealthfront.com\/wealthfronts-new-mission\/\">build a financial system that favors people, not institutions<\/a> in another way, check out our <a href=\"https:\/\/www.wealthfront.com\/careers\">careers page<\/a> for current opportunities!<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:separator -->\n<hr class=\"wp-block-separator\"\/>\n<!-- \/wp:separator -->\n\n<!-- wp:heading {\"level\":3} -->\n<h3>Disclosures<\/h3>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph {\"style\":{\"typography\":{\"fontSize\":\"14px\"}}} -->\n<p style=\"font-size:14px\">The information contained in this communication is provided for general informational purposes only, and should not be construed as investment or tax advice. Nothing in this communication should be construed as a solicitation or offer, or recommendation, to buy or sell any security. Any links provided to other server sites are offered as a matter of convenience and are not intended to imply that Wealthfront Advisers or its affiliates endorses, sponsors, promotes and\/or is affiliated with the owners of or participants in those sites, or endorses any information contained on those sites, unless expressly stated otherwise.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph {\"style\":{\"typography\":{\"fontSize\":\"14px\"}}} -->\n<p style=\"font-size:14px\">All investing involves risk, including the possible loss of money you invest, and past performance does not guarantee future performance. Please see our Full Disclosure for important details.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph {\"style\":{\"typography\":{\"fontSize\":\"14px\"}}} -->\n<p style=\"font-size:14px\">Wealthfront offers a free software-based financial advice engine that delivers automated financial planning tools to help users achieve better outcomes. Investment management and advisory services are provided by Wealthfront Advisers LLC, an SEC registered investment adviser, and brokerage related products are provided by Wealthfront Brokerage LLC, a member of FINRA\/SIPC.\u2002\u2002&nbsp;<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph {\"style\":{\"typography\":{\"fontSize\":\"14px\"}}} -->\n<p style=\"font-size:14px\">Wealthfront, Wealthfront Advisers and Wealthfront Brokerage are wholly owned subsidiaries of Wealthfront Corporation.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph {\"style\":{\"typography\":{\"fontSize\":\"14px\"}}} -->\n<p style=\"font-size:14px\">\u00a9 2022 Wealthfront Corporation. All rights reserved.<\/p>\n<!-- \/wp:paragraph -->","excerpt":"Over the past two years at Wealthfront we\u2019ve been building a design system from the ground up across all our frontend platforms: Android, iOS, and web. If you\u2019re a Wealthfront client you may have noticed sweeping, iterative visual changes across our apps as we introduced new components and migrated features to adopt these components. At... <a class=\"moretag\" href=\"https:\/\/eng.wealthfront.com\/2022\/05\/10\/building-wealthfronts-multi-platform-design-system\/\" aria-label=\"Read more about Building Wealthfront&#8217;s multi-platform design system\">Read more<\/a>","date":"2022-05-10 11:57:07","author":"Spencer Miskoviak","categories":["wealthfront engineering"],"tags":["android","design-systems","ios","web"]},{"id":2629,"title":"Halving iOS Test Time with Partitioning in Jenkins Pipelines","permalink":"https:\/\/eng.wealthfront.com\/2021\/02\/05\/halving-ios-test-time-with-partitioning-in-jenkins-pipelines\/","content":"<!-- wp:paragraph -->\n<p>At Wealthfront, testing is core to the culture of our engineering organization and business. In fact, on the iOS team, testing is woven into our development process. We host and manage our own continuous integration (CI) pipeline on Jenkins which runs our unit and UI test suites. As most iOS developers know, UI tests take an order of magnitude longer to run than unit tests since they simulate a user interacting with the app. Recently, our UI test suite reached an untenable size, with 90 tests taking over an hour and a half to execute. As we scale our codebase and team (FYI <a href=\"https:\/\/www.wealthfront.com\/careers\">we\u2019re hiring<\/a>), the need to minimize this execution time becomes paramount. To mitigate the cost of these tests, we decided to parallelize our UI test job across our CI build machines with the help of Jenkins Pipelines.&nbsp;<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading -->\n<h2><strong>Our Jenkins architecture<\/strong><\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Our CI environment contained 16 Mac Minis serving as Jenkins agents. Each merge to the git main branch kicked off three jobs: UI tests with mock data, UI tests hitting an isolated backend instance, and a master build, which itself parallelized the execution of our three unit test suites (Release, Beta, and Dev). This meant if no other jobs were running, such as a side branch build or an App Store deployment, only five of our 16 agents would be in use. These unused agents presented an opportunity to distribute the workload of our tests.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:image {\"align\":\"center\",\"id\":2636,\"sizeSlug\":\"large\"} -->\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-large\"><img src=\"https:\/\/eng.wealthfront.com\/wp-content\/uploads\/2021\/02\/iOS-CI-setup.svg_-2-1024x314.png\" alt=\"A diagram of our Jenkins setup\" class=\"wp-image-2636\"\/><figcaption>A diagram of our Jenkins setup<\/figcaption><\/figure><\/div>\n<!-- \/wp:image -->\n\n<!-- wp:heading -->\n<h2><strong>The plan to parallelize<\/strong><\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Parallelizing UI tests involved partitioning our UI test suite into a given number of divisions, running each division on an agent, and collecting the results once every division had finished. This seemed simple in theory but came with two big challenges during implementation: how do we construct the n-divisions of tests and how can we actually run each division in parallel?<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading -->\n<h2><strong>Dividing our tests<\/strong><\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Partitioning an array of tests is easy enough, but constructing the array in the first place posed a challenge. We knew our array should contain all test classes in our UI test scheme, since the testing tool we use in CI, <a href=\"https:\/\/docs.fastlane.tools\/actions\/scan\/\">Fastlane's scan<\/a>, can consume a list containing a subset of test cases to be run, of the form <code>Test Bundle[\/Test Suite[\/Test Case]], \u2026<\/code>\u00b9. This can be accomplished using the <code>only_testing<\/code> parameter, and in our case an example list might look like: <code>[WealthfrontUITests\/CollegeGoalUITest, WealthfrontUITests\/AccountTransferInitiationUITest, \u2026]<\/code>\u00b2.&nbsp;<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><!-- wp:code {\"language\":\"ruby\",\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>desc \u201cRun UI Tests on iPhone 11 Pro\u201d\nlane :ui_tests\n  tests_to_run = options[:only_testing]\n  clear_derived_data(derived_data_path: derived_data_path)\n  sh(\u201cxcrun simctl shutdown booted\u201d)\n  sh(\u201cxcrun simctl erase all\u201d)\n  sh(\u201cxcrun instruments -w \u2018iPhone 11 Pro (13.3) [\u2018 -t Blank\u201d)\n  scan(workspace: \u201cWealthfront.xcworkspace\u201d,\n    scheme: \u201cWealthfrontUITests\u201d,\n    output_style: \u201cbasic\u201d,\n    devices: [\u201ciPhone 11 Pro\u201d],\n    clean: true,\n    derived_data_path: derived_data_path,\n    skip_build: true,\n    only_testing: \"#{tests_to_run}\")\n  sh(\u201cxcrun simctl shutdown booted\u201d)<\/code><\/pre>\n<!-- \/wp:code --><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph {\"fontSize\":\"small\"} -->\n<p class=\"has-small-font-size\">A snippet of our Fastfile<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>But how might we get the full list of test cases to partition? As it stands, neither <code>xcodebuild<\/code> nor Fastlane provides a way of querying the tests in a given test bundle, so there's no help there. Facebook\u2019s <code>xctool<\/code> does enable this through the <code>-listTestsOnly<\/code> argument to <code>run-tests<\/code>, however incorporating another third party library and rolling it out to all of our agents seemed unnecessary for such a small task\u00b3. Since all of our UI test files were located in the same folder we decided it would be simpler to parse and collect the class name from each one at test time. This way, new test files would be implicitly included in the job, so long as each file was in the proper directory. Below is the function we developed to construct the full array:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><!-- wp:code {\"language\":\"groovy\",\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>def getAllUITestClassNames() {\n    def allSwiftFiles = sh(\n        script: \u201cfind WealthfrontUITests -name \u2018*.swift\u2019\u201d,\n        returnStdout: true\n    ).trim().split(\u2018\\n\u2019) as String[]\n    def directory = pwd()\n    def allClassNames = []\n    allSwiftFiles.each {\n        def file = readFile \u201c${directory}\/${it}\u201d\n        def matcher = (file =~ \/(?&lt;=class ).*?(?=:)\/)\n        if (matcher.count &gt; 0) {\n            allClassNames += matcher[0]\n        }\n    }\n    def testClassNames = allClassNames.findResults { \n        it.endsWith(\u201cUITest\u201d) || it.endsWith(\u201cIntegrationTest\u201d) ? it : null\n    }\n    return testClassNames\n}<\/code><\/pre>\n<!-- \/wp:code --><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>With the list of all test cases, we could then partition the list into n buckets of equal size (excluding the remainder if n did not divide the number of test cases), and move on to the execution of the tests.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><!-- wp:code {\"language\":\"groovy\",\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>def partitionTestClassNames(classNames, buckets) {\n    def subArrays = []\n    def bucketSize = classNames.size() \/ buckets\n    for (int i = 0; i &lt; buckets; i++) {\n        def start = i * bucketSize\n        def end = i == buckets - 1 ? classNames.size() : start + bucketSize\n        subArrays += [classNames[start..&lt;end]]\n    }\n    return subArrays\n}<\/code><\/pre>\n<!-- \/wp:code --><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading -->\n<h2><strong>Running in parallel<\/strong><\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Our UI test job was configured through the Jenkins classic UI and consisted mainly of a Build shell script which executed our Fastlane Lane. We were limited in that our job could not take advantage of <a href=\"https:\/\/jenkins.io\/blog\/2017\/09\/25\/declarative-1\/\">Jenkins\u2019 parallelization capabilities<\/a>. The only way to accomplish this was to move to a <a href=\"https:\/\/jenkins.io\/doc\/book\/pipeline\/jenkinsfile\/\">Jenkins Declarative Pipeline<\/a>, in which the building, testing, and deployment of our code all resided in a single <code>Jenkinsfile<\/code>. In addition to supporting parallel execution of stages, Declarative Pipelines have the added benefit of defining a job in Groovy code. Our Jenkins job was defined through the web interface which, though technically versioned, cannot be reviewed in the same manner as our source code. With Declarative pipelines, the process of building and testing our code lives side-by-side with our source code.&nbsp;<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>The first iteration of our <code>Jenkinsfile<\/code> consisted of two stages, the first of which constructed a partition of test cases using the logic described previously, while the second farmed out the divisions of the partition to run on three agents and reported the results. The following is an abbreviated version of our <code>Jenkinsfile<\/code>.&nbsp;<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><!-- wp:code {\"language\":\"groovy\",\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>partitioned_test_cases = []\n \npipeline {\n   environment {\n       TARGET_PARALLEL_AGENTS = 3\n   }\n \n   stages {\n       stage(\u201cPartition UI tests\u201d) {\n           steps {\n               script {\n                   def testClassNames = getAllUITestClassNames()\n                   def testClassNamesWithSuite = testClassNames.collect { \u201cWealthfrontUITests\/${it}\u201d }\n                   def partition = partitionTestClassNames(testClassNamesWithSuite,\n                                                           env.TARGET_PARALLEL_AGENTS as Integer)\n                   partitioned_test_cases = partition    \n               }\n           }\n       }\n \n       stage(\u2018Parallel Execution\u2019) {\n           steps {\n               runTestsInParallel()\n           }\n       }\n   }\n}\n \ndef runTestsInParallel() {\n   def numBranches = partitioned_test_cases.size()\n   def branches = [:]\n   for (int i = 0; i &lt; numBranches; i++) {\n       def branchName = \u201c${i + 1}\/${numBranches} branch\u201d\n       def index = i\n       branches[branchName] = {\n           node (\u2018iOS\u2019) {\n               sh(\u201cfastlane ui_tests only_testing:${testCases}\u201d)\n               junit(allowEmptyResults: true, testResults: \u2018fastlane\/test_output\/*.junit\u2019)\n           }\n       }\n   }\n \n   parallel branches\n}\t<\/code><\/pre>\n<!-- \/wp:code --><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Note: As you can see in the <code>runTestsInParallel<\/code> method, we dynamically construct the branches, each of which corresponds to a parallel execution of tests. Alternatively, we could have defined a fixed number of stages in place of the \u2018Parallel Execution\u2019 stage, but instead we now have the flexibility to update the target number of parallel agents with a single line change of our environment variable.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading -->\n<h2><strong>Measuring the results<\/strong><\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>With our UI test job fully defined, we could now see the impact of our improvements. The old job regularly ran longer than 90 minutes, while the updated job ran for only 40 minutes on average\u2074. Using only two more of our available agents, we gained more than a 50% improvement.&nbsp;<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>But is this the best we can do? You may have noticed that our partitioning logic simply slices our list of test cases, ordered by the output of <code>find<\/code> (which makes no ordering guarantee), into equal divisions weighted by number of test cases. Our divisions happened to be similar in execution time, but it\u2019s plausible that in the future, one arbitrary division might contain test cases with many long-running test methods. This would lead to a branch that would run much longer than the rest, extending the overall job and defeating the purpose of parallelization. Thus, if our goal was to minimize total execution time, we should aim to minimize the range of execution time between our branches.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading -->\n<h2><strong>One last improvement<\/strong><\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Enter the <a href=\"https:\/\/plugins.jenkins.io\/parallel-test-executor\">Parallel Test Executor Plugin<\/a>. This helpful Jenkins plugin analyzes the previous run of the job and partitions the job\u2019s tests weighted by execution time, meaning we can delegate the work of balancing our tests. The only work on our end then becomes tacking on any newly added tests (as they weren\u2019t part of the previous run) and formatting the plugin\u2019s output for consumption by Fastlane. Below is the abbreviated, second iteration of our <code>Jenkinsfile<\/code>.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><!-- wp:code {\"language\":\"groovy\",\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>partitioned_test_cases = []\n\npipeline {\n    agent { label \u2018iOS\u2019 }\n\n    environment {\n        TARGET_PARALLEL_AGENTS = 3\n    }\n\n    stages {\n        stage(\u2018Partition UI Tests\u2019) {\n            steps {\n                script {\n                    \/\/ Parallel Executor Plugin (PEP) returns one extra empty split if generateInclusions == true\n                    def parallelism = count((env.TARGET_PARALLEL_AGENTS as Integer) + 1)\n                    def splits = splitTests parallelism: parallelism, \n                                            estimateTestsFromFiles: true,\n                                            generateInclusions: true\n\n                    def includedSplitLists = splits.findAll { it.includes }.collect { it.list }\n                    \/\/ PEP adds Java extensions to test cases\n                    def formattedSplitLists = includedSplitLists.collect{ it.collect { it.minus(\u201c.class\u201d).minus(\u201c.java\u201d) }.unique() }\n\n                    if (formattedSplitLists.size() == 0) {\n                        \/\/ `splitTests` found nothing in the previous build\n                        (env.TARGET_PARALLEL_AGENTS as Integer).times {\n                            formattedSplitLists.push([])\n                        }\n                    }\n\n                    def allTestClassNames = getAllUITestClassNames()\n                    def testClassNamesWithScheme = allTestClassNames.collect { \u201cWealthfrontUITests\/${it}\u201d }\n\n                    def allTestClassNamesFromPreviousBuild = formattedSplitLists.flatten()\n                    def testClassNamesNotPreviouslyTested = testClassNamesWithScheme - allTestClassNamesFromPreviousBuild\n\n                    \/\/ Add residual tests not in previous build, or naively partition tests if plugin found no test results in previous build\n                    def index = 0\n                    for (int i = 0; i &lt; testClassNamesNotPreviouslyTested.size(); i++) {\n                        formattedSplitLists[index % formattedSplitLists.size()] += testClassNamesNotPreviouslyTested[i]\n                        index++\n                    }\n\n                    partitioned_test_cases = formattedSplitLists\n                }\n            }\n        }\n\n        stage(\u2018Parallel Execution\u2019) {\n            steps {\n                runTestsInParallel()\n            }\n        }\n    }\n}<\/code><\/pre>\n<!-- \/wp:code --><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>We actually observed little difference in total execution time with this new plugin, but we can be confident moving forward that our test job will automatically adjust and adapt as our test base scales.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading -->\n<h2><strong>Learnings &amp; Future Directions<\/strong><\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>In summary, we found how to leverage CI infrastructure to lower the cost of expensive jobs and realized the benefits of describing jobs in code. In the future, we will likely add one more level of parallelization by running UI tests concurrently on a single agent, <a href=\"https:\/\/developer.apple.com\/videos\/play\/wwdc2018\/403\/\">a process Apple made quite simple in Xcode 10<\/a>.&nbsp;<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>If this work interests you or if you have thoughts on how we might make it even better, check out our <a href=\"https:\/\/www.wealthfront.com\/careers\">careers page<\/a>!<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading -->\n<h2><strong>Resources<\/strong><\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:list -->\n<ul><li><a href=\"https:\/\/jenkins.io\/blog\/2016\/06\/16\/parallel-test-executor-plugin\/\">Faster Pipelines with the Parallel Test Executor Plugin<\/a><\/li><li><a href=\"https:\/\/jenkins.io\/doc\/book\/pipeline\/syntax\/\">Pipeline Syntax<\/a><\/li><li><a href=\"https:\/\/jenkins.io\/solutions\/pipeline\/\">Pipeline as Code with Jenkins<\/a><\/li><li><a href=\"https:\/\/developer.apple.com\/library\/archive\/technotes\/tn2339\/_index.html\">Technical Note TN2339: Building from the Command Line with Xcode FAQ<\/a><\/li><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:heading -->\n<h2><strong>Notes<\/strong><\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:list {\"ordered\":true} -->\n<ol><li>A note on testing nomenclature: Apple uses the <code>Test Target &gt; Test Class &gt; Test Method<\/code> naming convention to refer to the structure of tests in Xcode, while Fastlane uses <code>Test Bundle &gt; Test Suite &gt; Test Case<\/code> to refer to the same hierarchy. Hereafter, when we mention our UI test suite, we mean the collection of all UI test methods in our UI test bundle.<\/li><li>We could have partitioned our tests at the granularity of test method, however it would introduce the overhead of calling the class methods <code>XCTestCase.setup()<\/code> and <code>XCTestCase.tearDown()<\/code> once for each agent the test case was distributed on, instead of exactly once.<\/li><li><code>xctool<\/code> is also <a href=\"https:\/\/blog.travis-ci.com\/2018-08-15-deprecating-xctool\">losing support as an CI build tool.<\/a><\/li><li>Since there is a fixed overhead of building the application for testing on each agent that runs a subset of tests, we cannot expect the execution time to be cut by two-thirds to 30 minutes.<\/li><\/ol>\n<!-- \/wp:list -->\n\n<!-- wp:separator {\"className\":\"is-style-wide\"} -->\n<hr class=\"wp-block-separator is-style-wide\"\/>\n<!-- \/wp:separator -->\n\n<!-- wp:heading {\"level\":3} -->\n<h3><strong>Disclosure<\/strong><\/h3>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>This communication has been prepared solely for informational purposes only.&nbsp; Nothing in this communication should be construed as an offer, recommendation, or solicitation to buy or sell any security or a financial product.&nbsp; Any links provided to other server sites are offered as a matter of convenience and are not intended to imply that Wealthfront or its affiliates endorses, sponsors, promotes and\/or is affiliated with the owners of or participants in those sites, or endorses any information contained on those sites, unless expressly stated otherwise.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Wealthfront offers a free software-based financial advice engine that delivers automated financial planning tools to help users achieve better outcomes. Investment management and advisory services are provided by Wealthfront Advisers LLC, an SEC registered investment adviser, and brokerage related products are provided by Wealthfront Brokerage LLC, a member of&nbsp; <a href=\"https:\/\/www.finra.org\/#\/\">FINRA<\/a> \/ <a href=\"https:\/\/www.sipc.org\/\">SIPC<\/a> .&nbsp; &nbsp;<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Wealthfront, Wealthfront Advisers and Wealthfront Brokerage are wholly owned subsidiaries of Wealthfront Corporation.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>\u00a9 2021 Wealthfront Corporation. All rights reserved.<\/p>\n<!-- \/wp:paragraph -->","excerpt":"At Wealthfront, testing is core to the culture of our engineering organization and business. In fact, on the iOS team, testing is woven into our development process. We host and manage our own continuous integration (CI) pipeline on Jenkins which runs our unit and UI test suites. As most iOS developers know, UI tests take... <a class=\"moretag\" href=\"https:\/\/eng.wealthfront.com\/2021\/02\/05\/halving-ios-test-time-with-partitioning-in-jenkins-pipelines\/\" aria-label=\"Read more about Halving iOS Test Time with Partitioning in Jenkins Pipelines\">Read more<\/a>","date":"2021-02-05 18:47:07","author":"Adam Heimendinger","categories":["wealthfront engineering"],"tags":["ci","continuous-integration","ios","jenkins","parallelization","testing","ui-testing"]},{"id":2586,"title":"Streamline development with Custom DevTools","permalink":"https:\/\/eng.wealthfront.com\/2020\/12\/09\/streamline-development-with-custom-devtools\/","content":"<!-- wp:paragraph -->\n<p>Today, DevTools are ubiquitous when developing web apps because of their ability to inspect, debug, modify, automate, and more. While it can sometimes seem that having many distinct DevTools is unnecessary, each tool falls at a different point on the spectrum of developer needs. As needs become more specialized and focused, so do the tools. For example, Chrome DevTools focuses on the building blocks of any web app: HTML, CSS, and JavaScript. However, when working with something like React, it doesn\u2019t offer any insight into the components being rendered or their props. Instead, React provides its own DevTools to work with components and props. In fact, it\u2019s possible for DevTools to operate at an even more specific level. However, such tools are often overlooked because these needs are usually app-specific.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:image {\"id\":3627,\"sizeSlug\":\"full\",\"linkDestination\":\"none\"} -->\n<figure class=\"wp-block-image size-full\"><img src=\"https:\/\/eng.wealthfront.com\/wp-content\/uploads\/2025\/08\/DevTools-1.png\" alt=\"\" class=\"wp-image-3627\"\/><\/figure>\n<!-- \/wp:image -->\n\n<!-- wp:paragraph -->\n<p>Throughout development, we frequently fill forms, create fake users, log in, or simulate other user interactions. Each of these tasks may only take a few seconds, but this quickly adds up. What if there were DevTools that operated at this more specific level?<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading -->\n<h2 class=\"wp-block-heading\">Custom DevTools<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>By creating custom DevTools specific to an app, they can operate at an even higher abstraction to handle things like user interactions, or debugging tracking events. While this requires building and maintaining the custom DevTools, it also means it can be tailored to the needs of the app and engineers to streamline development.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:image {\"id\":3628,\"sizeSlug\":\"full\",\"linkDestination\":\"none\",\"align\":\"center\"} -->\n<figure class=\"wp-block-image aligncenter size-full\"><img src=\"https:\/\/eng.wealthfront.com\/wp-content\/uploads\/2025\/08\/DevTools-2.png\" alt=\"\" class=\"wp-image-3628\"\/><\/figure>\n<!-- \/wp:image -->\n\n<!-- wp:paragraph -->\n<p>For example, our custom DevTools provides general information about the current git branch as well as Continuous Integration build status. There is also a section for contextual actions \u2013 such as filling a name input \u2013 that only appear if the action can be performed on the given page. Finally, there are a few options to highlight the components on the page within the design system, log tracking events, and auto-open the custom DevTools anytime a new action appears. As our needs change and the app changes, the custom DevTools evolve too.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading -->\n<h2 class=\"wp-block-heading\">Implementation<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>There are many ways custom DevTools could be implemented. For example, it could be a browser extension or built directly into the app. At Wealthfront, we decided to build it directly in the app to avoid the downsides of browser extensions. A browser extension would require additional tooling, has limitations interacting with the page, is browser specific, and requires installation. On the other hand, building DevTools in the app can take advantage of the existing tooling, works in any browser, and requires no additional installation.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Our custom DevTools starts with a single file that can be imported on any page in the app and initialized by invoking the <code>install<\/code> function it exports. This appends a new element to the document body and renders a React component. This component contains all the features and functionality.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:shortcode -->\n<!-- wp:code {\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>import React from 'react';\nimport ReactDOM from \"react-dom\";\n\nfunction DevTools() {\n  return &lt;div&gt;You now have your own DevTools!&lt;\/div&gt;;\n}\n\nexport function install() {\n  const devToolsRoot = document.createElement(\"div\");\n  document.body.appendChild(devToolsRoot);\n  ReactDOM.render(&lt;DevTools \/&gt;, devToolsRoot);\n}<\/code><\/pre>\n<!-- \/wp:code -->\n<!-- \/wp:shortcode -->\n\n<!-- wp:paragraph -->\n<p>Rather than using a standard static import, the custom DevTools are imported using a <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Statements\/import#Dynamic_Imports\">dynamic import<\/a>. With this approach, bundlers like webpack can split this into its own chunk to reduce bundle size. This way it can be conditionally imported, for example only in the development environment.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:shortcode -->\n<!-- wp:code {\"language\":\"typescript\",\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>function loadDevtools() {\n  if (process.env.NODE_ENV === 'development') {\n    return import('dev_tools').then((devTools) =&gt; devTools.install());\n  }\n}<\/code><\/pre>\n<!-- \/wp:code -->\n<!-- \/wp:shortcode -->\n\n<!-- wp:paragraph -->\n<p>These code snippets provide a basic skeleton to add features to DevTools.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading -->\n<h2 class=\"wp-block-heading\">Features<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Below are several examples of custom DevTools features that we\u2019ve added over time and have found useful at Wealthfront.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":3} -->\n<h3 class=\"wp-block-heading\">User Actions<\/h3>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>One of the first things added were functions to perform different user actions. For example, if there was a field for first and last name, the function could find the input element based on the label text and fill it using the <a href=\"https:\/\/testing-library.com\/\">Testing Library<\/a> package. However, this quickly became problematic as the list of actions grew. Most actions are specific to one page, but would always show up. Instead of always showing all actions, logic was added to only show actions that were relevant to that page.&nbsp;<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:image {\"id\":3629,\"sizeSlug\":\"full\",\"linkDestination\":\"none\",\"align\":\"center\"} -->\n<figure class=\"wp-block-image aligncenter size-full\"><img src=\"https:\/\/eng.wealthfront.com\/wp-content\/uploads\/2025\/08\/DevTools-3.png\" alt=\"\" class=\"wp-image-3629\"\/><\/figure>\n<!-- \/wp:image -->\n\n<!-- wp:paragraph -->\n<p>The actions started to all follow a similar basic pattern. First, determine if the action is relevant to the current page. Next, show a button to perform the action if it is relevant. Finally, add logic to perform the action on the current page when the button is clicked. As a result, we standardized a general API to perform any action:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:list -->\n<ul class=\"wp-block-list\"><!-- wp:list-item -->\n<li><code>canPerform<\/code> - can this action be performed given the current state of the app? For example, to fill a name, does an input with \u201cfirst name\u201d and \u201clast name\u201d exist on the page?<\/li>\n<!-- \/wp:list-item -->\n\n<!-- wp:list-item -->\n<li><code>name<\/code> - the prompt displayed in the button to trigger the action. For example, to fill a name and continue to the next step it would be \u201cFill Name &amp; Next\u201d.<\/li>\n<!-- \/wp:list-item -->\n\n<!-- wp:list-item -->\n<li><code>perform<\/code> - perform the action. For example, find the \u201cfirst name\u201d and \u201clast name\u201d input and fill each.<\/li>\n<!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:shortcode -->\n<!-- wp:code {\"language\":\"typescript\",\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>\/\/ actions\/name.ts\nimport { screen } from '@testing-library\/react';\nimport userEvent from '@testing-library\/user-event';\n\nexport function canPerform() {\n  try {\n    return !!screen.getByLabelText(\/first name\/i) &amp;&amp; !!screen.getByLabelText(\/last name\/i);\n  } catch (error) {\n    return false;\n  }\n}\n\nexport async function perform() {\n  try {\n    await userEvent.type(await screen.findByLabelText(\/first name\/i), 'Wealth');\n    await userEvent.type(await screen.findByLabelText(\/last name\/i), 'Front');\n    await userEvent.click(await screen.findByText('Next'));\n  } catch (error) {\n    \/\/ handle the error\n  }\n}\n\nexport const name = 'Fill Name &amp; Next';<\/code><\/pre>\n<!-- \/wp:code -->\n<!-- \/wp:shortcode -->\n\n<!-- wp:paragraph -->\n<p>Then, this action is imported into an actions index file with all the other actions. This contains a list of all the possible actions and helpers to determine which actions are relevant to the current page. A new action can be quickly added by defining the three exports and adding it to this list.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:shortcode -->\n<!-- wp:code {\"language\":\"typescript\",\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>\/\/ actions\/index.ts\nimport * as name from '.\/actions\/name';\n\/\/ other action imports ...\n\nexport interface Action {\n  name: string;\n  canPerform(): boolean;\n  perform(): void;\n}\n\nconst ACTIONS: Action[] = [\n  name,\n  \/\/ other actions...\n];\n\nexport function calculateActions(): Action[] {\n  return ACTIONS.filter((action) =&gt; action.canPerform());\n}<\/code><\/pre>\n<!-- \/wp:code -->\n<!-- \/wp:shortcode -->\n\n<!-- wp:paragraph -->\n<p>From here the DevTools determines which actions to show by calling <code>calculateActions<\/code>. Unfortunately, things can change on the page in any number of ways. To handle this, the DevTools uses a <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/MutationObserver\">MutationObserver<\/a> so any time anything on the page changes, it recalculates what actions can be performed. Finally, this is all wrapped up in a <code>useActions<\/code> React hook that can be consumed by a component to render the relevant actions as buttons in the DevTools.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:shortcode -->\n<!-- wp:code {\"language\":\"typescript\",\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>import { Action, calculateActions, arrayContentsEqual } from '.\/actions';\n\nexport const useActions = (): Action[] =&gt; {\n  const [actions, setActions] = React.useState(calculateActions());\n\n  React.useEffect(() =&gt; {\n    function callback() {\n      const newActions = calculateActions();\n      if (!arrayContentsEqual(newActions, actions)) {\n        setActions(newActions);\n      }\n    }\n\n    const observer = new MutationObserver(callback);\n    observer.observe(document.body, { childList: true, subtree: true });\n\n    return () =&gt; observer.disconnect();\n  });\n\n  return actions;\n};<\/code><\/pre>\n<!-- \/wp:code -->\n<!-- \/wp:shortcode -->\n\n<!-- wp:paragraph -->\n<p>Now, if there are fields with a label of \u201cfirst name\u201d and \u201clast name\u201d the action will be displayed and performed by clicking the button. This pattern has worked well for quickly adding new actions and only showing the ones that are relevant. It\u2019s also flexible and handles a broad range of actions, well beyond filling an input.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":3} -->\n<h3 class=\"wp-block-heading\">Current Branch &amp; Build Status<\/h3>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Custom DevTools can do more than simulate user actions. It can also aggregate other useful information. It displays the current git branch <em>(for those pesky times something isn\u2019t working as expected only to realize it\u2019s the wrong branch<\/em>) and the branch\u2019s build status (<em>running, success, failure<\/em>). This metadata requires git commands and API calls so it\u2019s provided by an endpoint. There's a simple method defined to retrieve the current git branch with Ruby.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:shortcode -->\n<!-- wp:code {\"language\":\"ruby\",\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>def current_branch\n  `git rev-parse --abbrev-ref HEAD`.strip\nend<\/code><\/pre>\n<!-- \/wp:code -->\n<!-- \/wp:shortcode -->\n\n<!-- wp:heading {\"level\":3} -->\n<h3 class=\"wp-block-heading\">Component Highlighter<\/h3>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>At Wealthfront, we\u2019ve developed a standardized set of components such as cards, buttons, and typography as a part of our design system. It\u2019s not always obvious which content is a component from the design system and which is not. The DevTools has a \u201ccomponent highlighter\u201d option to solve this. When this option is enabled, it will draw a box around any component in our design system. This allows engineers, designers, product managers, and others to quickly see which components are from the design system. It can help surface which pages should rely more on design system components, or where there may be gaps in component coverage.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:image {\"id\":3630,\"sizeSlug\":\"full\",\"linkDestination\":\"none\"} -->\n<figure class=\"wp-block-image size-full\"><img src=\"https:\/\/eng.wealthfront.com\/wp-content\/uploads\/2025\/08\/DevTools-4.png\" alt=\"\" class=\"wp-image-3630\"\/><\/figure>\n<!-- \/wp:image -->\n\n<!-- wp:paragraph -->\n<p>To achieve this, each component in the design system has a <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTML\/Global_attributes\/data-*\">custom data attribute<\/a> on the underlying element that it renders so that it can be used to query by. A temporary highlighter element is created that surrounds the underlying component element. This solution isn\u2019t perfect, but strikes a good balance between simplicity and solving this use case. With this approach, there\u2019s nothing React specific, so it can work with any design system.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:shortcode -->\n<!-- wp:code {\"language\":\"typescript\",\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>const HIGHLIGHT_WIDTH = 2;\nconst HIGHLIGHT_SELECTOR = '[data-component]';\n\nfunction createHighlighterForElement(component: HTMLElement) {\n  const highlighter = document.createElement('div');\n\n  const { x, y, width, height } = component.getBoundingClientRect();\n  highlighter.style.cssText = `\n    top: ${y + window.scrollY - HIGHLIGHT_WIDTH}px;\n    left: ${x - HIGHLIGHT_WIDTH}px;\n    height: ${height}px;\n    width: ${width}px;\n    border: ${HIGHLIGHT_WIDTH}px solid red;\n    position: absolute;\n  `;\n\n  return highlighter;\n}\n\nfunction highlightComponents() {\n  const components = document.querySelectorAll&lt;HTMLElement&gt;(HIGHLIGHT_SELECTOR);\n\n  components.forEach((component) =&gt; {\n    const highlighter = createHighlighterForElement(component);\n    document.body.appendChild(highlighter);\n  });\n}<\/code><\/pre>\n<!-- \/wp:code -->\n<!-- \/wp:shortcode -->\n\n<!-- wp:heading {\"level\":3} -->\n<h3 class=\"wp-block-heading\">Local User Management<\/h3>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Finally, when developing locally, we often need to test users in a variety of states. This requires manually creating multiple different users. Previously, this required clicking through our signup flow, or using the command line. The first approach is an easier but slow process, while the second is much faster but requires passing the correct arguments.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Now, the DevTools streamline both of these flows. First, actions can be created for each step of the signup flow, which makes it much quicker. Second, a basic UI was added to replace the CLI for local user creation and management. Since these are fake users that only exist in our local environment the credentials can be stored in the browser\u2019s <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Window\/localStorage\">local storage<\/a> for easy retrieval to quickly switch between different fake users.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading -->\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>While there are many different DevTools freely and readily available, building your own custom DevTools can be a far more effective way to automate tedious app-specific development chores. This post has highlighted a number of DevTools features we\u2019ve built and found useful in our local development at Wealthfront. The next time you find yourself performing a repetitive task, feeling something takes a few too many clicks, or thinking there\u2019s other useful information that could be better surfaced, perhaps it\u2019s the right time to add custom DevTools of your own.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading -->\n<h2 class=\"wp-block-heading\">Disclosure<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>This communication has been prepared solely for informational purposes only.&nbsp; Nothing in this communication should be construed as an offer, recommendation, or solicitation to buy or sell any security or a financial product.&nbsp; Any links provided to other server sites are offered as a matter of convenience and are not intended to imply that Wealthfront or its affiliates endorses, sponsors, promotes and\/or is affiliated with the owners of or participants in those sites, or endorses any information contained on those sites, unless expressly stated otherwise.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Wealthfront offers a free software-based financial advice engine that delivers automated financial planning tools to help users achieve better outcomes. Investment management and advisory services are provided by Wealthfront Advisers LLC, an SEC registered investment adviser, and brokerage related products are provided by Wealthfront Brokerage LLC, a member of FINRA\/SIPC.\u2002\u2002&nbsp;<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Wealthfront, Wealthfront Advisers and Wealthfront Brokerage are wholly owned subsidiaries of Wealthfront Corporation.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>\u00a9 2020 Wealthfront Corporation. All rights reserved.<\/p>\n<!-- \/wp:paragraph -->","excerpt":"Today, DevTools are ubiquitous when developing web apps because of their ability to inspect, debug, modify, automate, and more. While it can sometimes seem that having many distinct DevTools is unnecessary, each tool falls at a different point on the spectrum of developer needs. As needs become more specialized and focused, so do the tools.... <a class=\"moretag\" href=\"https:\/\/eng.wealthfront.com\/2020\/12\/09\/streamline-development-with-custom-devtools\/\" aria-label=\"Read more about Streamline development with Custom DevTools\">Read more<\/a>","date":"2020-12-09 16:40:23","author":"Spencer Miskoviak","categories":["wealthfront engineering"],"tags":["devtools","web"]},{"id":2745,"title":"Building a truly accessible clickable div","permalink":"https:\/\/eng.wealthfront.com\/2020\/10\/01\/building-a-truly-accessible-clickable-div\/","content":"<!-- wp:paragraph -->\n<p>When developing features at Wealthfront, it is somewhat common to receive a design mock where an entire card is clickable. One example is the account card on a client\u2019s dashboard.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:image -->\n<figure class=\"wp-block-image\"><img src=\"https:\/\/lh4.googleusercontent.com\/mTx9uWW4NfSsUGBPv7jxItNWMcaYiX1-4wFU1ulxZ7UMrmtvldbmX4B3s7XPbKe_tVqcwNQfOjFp90GTZjD5ScnjmNVX46_8F9F-c-B59QUFAjr2IHX2tX9BIquLYFXp10Gds5dD\" alt=\"\"\/><\/figure>\n<!-- \/wp:image -->\n\n<!-- wp:paragraph -->\n<p>The link itself is highlighted by our purple primary text, and clicking it takes you to the corresponding Wealthfront account page. In fact, clicking anywhere on the account card takes you to this page as well.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading -->\n<h2>Accessibility challenges<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Adding this functionality seems pretty simple, and while we use React at Wealthfront, this can be done in a similar manner using (or not using) almost any framework. We can simply add an onClick handler to our account card div. While screen readers and tab indexes won\u2019t be aware of this new link, that\u2019s actually fine, since the accessible link also exists within the div. It would look something like<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:shortcode -->\n<!-- wp:code {\"language\":\"javascript\",\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>function AccountCard(props) {\n  function handleClick(e) {\n    redirect(\u2018account\u2019, props.account.accountId)\n  }\n\n  return (\n    &lt;div onClick={handleClick}&gt;\n      \/\/ account card content\n    &lt;\/div&gt;\n  );\n}<\/code><\/pre>\n<!-- \/wp:code -->\n<!-- \/wp:shortcode -->\n\n<!-- wp:paragraph -->\n<p>However we initially experienced two challenges with this method.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:list -->\n<ul><li>We need to add a lot of onClick handlers throughout the codebase, so it would be preferable to abstract this logic away somehow.<\/li><li>We need to support clickable cards that do not have a visible nested link or button.<\/li><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>In the latter case, screen readers were unable to properly identify our new link.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading -->\n<h2>A common but incomplete solution<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Most of the example solutions we found online for these or similar problems involved adding button-like functionality to the div. For example, we could create a ClickableContainer component.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:shortcode -->\n<!-- wp:code {\"language\":\"javascript\",\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>function ClickableContainer(props) {\n  \/\/ ideally these are stored in a separate key mapping utility file\n  const enterKey = 13;\n  const spaceKey = 32;\n  const { children, ariaLabel, ...otherProps } = props;\n\n  function handleKeyDown(e) {\n    if (e.keyCode === enterKey || e.keyCode === spaceKey) {\n      e.preventDefault();\n      props.onClick(e);\n    }\n  }\n\n  return (\n    &lt;div\n      onKeyDown={handleKeyDown}\n      role=\"button\"\n      tabIndex=\"0\"\n      aria-label={ariaLabel}\n      {...otherProps}\n    &gt;\n      {children}\n    &lt;\/div&gt;\n  );\n}<\/code><\/pre>\n<!-- \/wp:code -->\n<!-- \/wp:shortcode -->\n\n<!-- wp:paragraph -->\n<p>By adding key handling, a tab index, a role and an aria-label, this div essentially becomes an accessible button and then we can add whatever content we want within the button. This solution works great for simple components that actually look and function like a button. Although in that case, it likely makes more sense to use the more semantic &lt;button \/&gt;.&nbsp;<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Unfortunately, this approach is less effective with complex components. Let\u2019s take another look at the account card component mentioned above.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:image -->\n<figure class=\"wp-block-image\"><img src=\"https:\/\/lh4.googleusercontent.com\/mTx9uWW4NfSsUGBPv7jxItNWMcaYiX1-4wFU1ulxZ7UMrmtvldbmX4B3s7XPbKe_tVqcwNQfOjFp90GTZjD5ScnjmNVX46_8F9F-c-B59QUFAjr2IHX2tX9BIquLYFXp10Gds5dD\" alt=\"\"\/><\/figure>\n<!-- \/wp:image -->\n\n<!-- wp:paragraph -->\n<p>If we were to make the whole card a button, screen readers would have difficulty reading its content to the user. For example, providing an aria-label, something like \u201cMy Personal Investment Account\u201d, would result in the reader stating only that and skipping other valuable information like the current balance. Not including an aria-label would instead result in the reader stating all the content for the button at once, which could be overwhelming and unhelpful in describing the button\u2019s purpose. In addition, this solution works only for mimicking a button. In the account card example listed above, we really want a link since we\u2019re performing a navigation. With this approach, there doesn\u2019t seem to be any straightforward way to capture that discrepancy.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Another potential drawback is that sometimes we have a clickable card that actually contains multiple links or buttons. For example, let\u2019s look at another card promoting our new Autopilot feature.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:image -->\n<figure class=\"wp-block-image\"><img src=\"https:\/\/lh4.googleusercontent.com\/gcN-Pb9FBI-8lI6ApDiuSEidNjRqMdWy-PXkPh7bblkEppCWnJXC9gTMDO3VEoS8PwH6H0JKCFQLMTnULxIMmGvcZ2xPWyGdiaIQnCdi0XKywBhjjCAfC5Mrlx5TGoTqFLaGkzsO\" alt=\"\"\/><\/figure>\n<!-- \/wp:image -->\n\n<!-- wp:paragraph -->\n<p>In this instance, we have the \u201cHow it works\u201d link that takes you to the Autopilot page and clicking anywhere on the card will do the same. Anywhere, that is, except the X which dismisses the card. If we were to make this entire card a button, there would&nbsp; actually be no good way for a screen reader or keyboard to navigate to the close button since it would live inside of the card that would then itself behave like a button.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading -->\n<h2>The accessible solution<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>We solved these problems by creating an ExpandedClickableArea component.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:shortcode -->\n<!-- wp:code {\"language\":\"javascript\",\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>function ExpandedClickableArea(props) {\n  const clickableElemTypes = ['a', 'button', 'input'];\n  const refExpandedArea = useRef();\n\n  function handleClick(e) {\n    const clickableElems = [\n      ...refExpandedArea.current.querySelectorAll('[data-expand-click-area]')\n    ];\n    if (clickableElems.length !== 1) {\n      throw new Error(\n        `Expected one clickable element but found ${clickableElems.length}`\n      );\n    }\n    const clickableElem = clickableElems[0];\n    const targetIsClickable = clickableElemTypes.includes(e.target.tagName.toLowerCase());\n\n    if (clickableElem !== e.target &amp;&amp; !targetIsClickable) {\n      clickableElem.click();\n    }\n  }\n\n  return (\n    &lt;div\n      ref={refExpandedArea}\n      className={`${props.className} ${styles.expandedClickableArea}`}\n      tabIndex=\u201d0\u201d\n      onClick={handleClick}\n    &gt;\n      {props.children}\n    &lt;\/div&gt;\n  );\n}<\/code><\/pre>\n<!-- \/wp:code -->\n<!-- \/wp:shortcode -->\n\n<!-- wp:paragraph -->\n<p>Our ExpandedClickableArea component is still a standard &lt;div \/&gt; and will be treated as such for accessibility purposes. But it will now look for a nested DOM node with a data-expand-click-area attribute and forward any clicks to that element. We also take some precautions here to assure that the element is in fact clickable (an a, button, or input) and that we haven\u2019t already clicked on a link or button (since in that case, the corresponding action is already taken). This also prevents us from navigating to the Autopilot page when the X button is clicked.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>But there\u2019s still one use case we haven\u2019t accounted for -- when we have a clickable area without an explicit nested link (or button) to expand. Well there\u2019s a simple solution for that as well. Inside the component we can simply add a link or button component and add a sr-only class. We can then add a tabIndex=\u201d-1\u201c and some styling that makes the element only viewable to screen readers.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:shortcode -->\n<!-- wp:code {\"language\":\"scss\",\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>.sr-only {\n  position: absolute;\n  width: 1px;\n  height: 1px;\n  margin: -1px;\n  padding: 0;\n  overflow: hidden;\n  clip: rect(0, 0, 0, 0);\n  border: none;\n}<\/code><\/pre>\n<!-- \/wp:code -->\n<!-- \/wp:shortcode -->\n\n<!-- wp:paragraph -->\n<p>Even better, if you have a shared &lt;Button \/&gt; or &lt;Link \/&gt; component you can just add an isSrOnly prop that adds the class and tabindex for you. And then we once again have an accessible and fully functional ExpandedClickableArea component.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>If this seems interesting to you, take a look at the Wealthfront <a href=\"https:\/\/www.wealthfront.com\/careers\">careers<\/a> page and come help us continue building amazing and accessible products! You can also learn more about <a href=\"https:\/\/www.wealthfront.com\/\">Wealthfront<\/a>, or about our <a href=\"https:\/\/blog.wealthfront.com\/meet-autopilot-a-new-service-that-automates-your-savings-strategy\/\">new Autopilot feature<\/a> discussed above.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading -->\n<h2>Disclosure<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>This communication has been prepared solely for informational purposes only.&nbsp; Nothing in this communication should be construed as an offer, recommendation, or solicitation to buy or sell any security or a financial product.&nbsp; Any links provided to other server sites are offered as a matter of convenience and are not intended to imply that Wealthfront or its affiliates endorses, sponsors, promotes and\/or is affiliated with the owners of or participants in those sites, or endorses any information contained on those sites, unless expressly stated otherwise.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Wealthfront offers a free software-based financial advice engine that delivers automated financial planning tools to help users achieve better outcomes. Investment management and advisory services are provided by Wealthfront Advisers LLC, an SEC registered investment adviser, and brokerage related products are provided by Wealthfront Brokerage LLC, a member of <a href=\"https:\/\/www.finra.org\/#\/\">FINRA<\/a>\/<a href=\"https:\/\/www.sipc.org\/\">SIPC<\/a>.\u2002\u2002&nbsp;<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Wealthfront, Wealthfront Advisers and Wealthfront Brokerage are wholly owned subsidiaries of Wealthfront Corporation.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>\u00a9 2020 Wealthfront Corporation. All rights reserved.<\/p>\n<!-- \/wp:paragraph -->","excerpt":"When developing features at Wealthfront, it is somewhat common to receive a design mock where an entire card is clickable. One example is the account card on a client\u2019s dashboard. The link itself is highlighted by our purple primary text, and clicking it takes you to the corresponding Wealthfront account page. In fact, clicking anywhere... <a class=\"moretag\" href=\"https:\/\/eng.wealthfront.com\/2020\/10\/01\/building-a-truly-accessible-clickable-div\/\" aria-label=\"Read more about Building a truly accessible clickable div\">Read more<\/a>","date":"2020-10-01 14:57:34","author":"Andrew Easton","categories":["wealthfront engineering"],"tags":["a11y","accessibility","web"]},{"id":2080,"title":"Checkstyle at Wealthfront","permalink":"https:\/\/eng.wealthfront.com\/2016\/10\/26\/checkstyle-at-wealthfront\/","content":"<p id=\"CheckstyleatWealthfront-WhatisCheckstyle?\">At Wealthfront we\u00a0use Checkstyle to enforce some coding standards, also to detect bad practice at the early stage. In this blog post, we will describe how to configure Checkstyle and how to customize that for our needs.<\/p>\n\n<h3 id=\"CheckstyleatWealthfront-HowtoconfigureCheckstyle?\">How to configure Checkstyle?<\/h3>\nThere are three components working together to automate all these checks:\n<ul>\n \t<li><a class=\"external-link\" href=\"http:\/\/checkstyle.sourceforge.net\/\" rel=\"nofollow\">checkstyle<\/a>: the tool to perform the style check<\/li>\n \t<li><a class=\"external-link\" href=\"http:\/\/maven.apache.org\/plugins\/maven-checkstyle-plugin\/index.html\" rel=\"nofollow\">maven-checkstyle-plugin<\/a>: the plugin to integrate with checkstyle<\/li>\n \t<li>wealthfront-test-commons: the customized checks<\/li>\n<\/ul>\n<!-- wp:code {\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>    &lt;plugins&gt;\n      &lt;plugin&gt;\n        &lt;groupId&gt;org.apache.maven.plugins&lt;\/groupId&gt;\n        &lt;artifactId&gt;maven-checkstyle-plugin&lt;\/artifactId&gt;\n        &lt;version&gt;2.17&lt;\/version&gt;\n        &lt;dependencies&gt;\n          &lt;dependency&gt;\n            &lt;groupId&gt;com.puppycrawl.tools&lt;\/groupId&gt;\n            &lt;artifactId&gt;checkstyle&lt;\/artifactId&gt;\n            &lt;version&gt;6.12.1&lt;\/version&gt;\n          &lt;\/dependency&gt;\n          &lt;dependency&gt;\n            &lt;groupId&gt;com.wealthfront&lt;\/groupId&gt;\n            &lt;artifactId&gt;wealthfront-test-commons&lt;\/artifactId&gt;\n            &lt;version&gt;${wealthfront-test-commons-version}&lt;\/version&gt;\n          &lt;\/dependency&gt;\n        &lt;\/dependencies&gt;\n        &lt;executions&gt;\n          &lt;execution&gt;\n            &lt;id&gt;validate&lt;\/id&gt;\n            &lt;phase&gt;validate&lt;\/phase&gt;\n            &lt;configuration&gt;\n              &lt;configLocation&gt;wfstyle.xml&lt;\/configLocation&gt;\n              &lt;encoding&gt;UTF-8&lt;\/encoding&gt;\n              &lt;consoleOutput&gt;true&lt;\/consoleOutput&gt;\n              &lt;failsOnError&gt;true&lt;\/failsOnError&gt;\n              &lt;includes&gt;**\/*.java&lt;\/includes&gt;\n              &lt;includeTestSourceDirectory&gt;true&lt;\/includeTestSourceDirectory&gt;\n              &lt;suppressionsLocation&gt;src\/checkstyle_suppressions.xml&lt;\/suppressionsLocation&gt;\n              &lt;cacheFile&gt;${basedir}\/checkstyle-cachefile&lt;\/cacheFile&gt;\n            &lt;\/configuration&gt;\n            &lt;goals&gt;\n              &lt;goal&gt;check&lt;\/goal&gt;\n            &lt;\/goals&gt;\n          &lt;\/execution&gt;\n        &lt;\/executions&gt;\n      &lt;\/plugin&gt;<\/code><\/pre>\n<!-- \/wp:code -->\n\nFew things to note in that plugin configuration:\n<ul>\n \t<li>It is bound to the \"validate\" phase and will check the code prior to compiling the code.<\/li>\n \t<li>We put cacheFile in ${basedir} instead of in target which can\u00a0save some time when running check over and over.<\/li>\n<\/ul>\n<h3 id=\"CheckstyleatWealthfront-Howdoescheckstylework?\">How does checkstyle work?<\/h3>\nThere are two types of checks that Checkstyle supports.\n<h4 id=\"CheckstyleatWealthfront-com.puppycrawl.tools.checkstyle.api.Check\">com.puppycrawl.tools.checkstyle.api.FileSetCheck<\/h4>\nIt reads the file and builds a list of String, then passes the File itself with the list of String to FileSetCheck#process(). Also, we have a meta test,\u00a0BadSnippetsFileSetCheck, which extends FileSetCheck and does regular expression matching against file content.\n<h4 id=\"CheckstyleatWealthfront-com.puppycrawl.tools.checkstyle.api.Check\">com.puppycrawl.tools.checkstyle.api.Check<\/h4>\nIt parses the file and builds an abstract syntax tree, then runs the depth-first traversal starting from the\u00a0root, and calls Check#visitToken() for each token.\n<h3 id=\"CheckstyleatWealthfront-com.puppycrawl.tools.checkstyle.api.FileSetCheck\">How to add new checks?<\/h3>\n<h4>RegexpSinglelineJava<\/h4>\nThe fastest and simplest way? We can add a new module for what should not be used in the code.\n\n<!-- wp:code {\"language\":\"markup\",\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>&lt;!DOCTYPE module PUBLIC\n    \"-\/\/Puppy Crawl\/\/DTD Check Configuration 1.3\/\/EN\"\n    \"http:\/\/www.puppycrawl.com\/dtds\/configuration_1_3.dtd\"&gt;\n&lt;module name=\"Checker\"&gt;\n  &lt;module name=\"TreeWalker\"&gt;\n    &lt;module name=\"RegexpSinglelineJava\"&gt;\n      &lt;property name=\"format\" value=\"YearMonthDay\"\/&gt;\n      &lt;property name=\"ignoreComments\" value=\"true\"\/&gt;\n      &lt;property name=\"message\" value=\"Always use LocalDate instead of YearMonthDay.\"\/&gt;\n    &lt;\/module&gt;\n  &lt;\/module&gt;\n&lt;\/module&gt;<\/code><\/pre>\n<!-- \/wp:code -->\n<h4>BadSnippetsFileSetCheck<\/h4>\nIf we need some advanced matching or multiline matching, we can add a new snippet in JavaBadCodeSnippetsTest.java. The \"value\" is the regular expression that should not appear in the code, and \"rationale\" will be shown to the users if checkstyle detects such violations.\n\n<!-- wp:code {\"language\":\"java\",\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>package com.wealthfront.test.filesetcheck;\n\nimport com.wealthfront.test.filesetcheck.BadSnippetsFileSetCheck.CodeSnippets;\nimport com.wealthfront.test.filesetcheck.BadSnippetsFileSetCheck.Snippet;\n\n@CodeSnippets(fileExtension = \"java\", snippets = {\n\n    @Snippet(value = \"\\\\n(?!@Transactional.*)\\\\n.*(?!abstract\\\\b).*extends Task\\\\b[^&gt;]\",\n        rationale = \"Tasks must be transactional.\"),\n\n})\npublic class JavaBadCodeSnippetsTest {\n}<\/code><\/pre>\n<!-- \/wp:code -->\n<h4>AbstractFileSetCheck<\/h4>\nWant to do something more based on the content? We can extend AbstractFileSetCheck and implement processFiltered(). It iterates through the file content, and if there are multiple empty lines, it shows error messages.\n\n<!-- wp:code {\"language\":\"java\",\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>package com.wealthfront.test.filesetcheck;\n\nimport java.io.File;\nimport java.util.List;\n\nimport com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck;\nimport com.puppycrawl.tools.checkstyle.api.CheckstyleException;\nimport com.puppycrawl.tools.checkstyle.api.FileContents;\nimport com.puppycrawl.tools.checkstyle.api.FileText;\n\npublic class MultipleEmptyLinesFileSetCheck extends AbstractFileSetCheck {\n\n  @Override\n  protected void processFiltered(File file, List&lt;String&gt; lines) throws CheckstyleException {\n    if (file.getPath().endsWith(\".java\")) {\n      final int countLessOne = lines.size() - 1;\n      final FileContents contents = new FileContents(FileText.fromLines(file, lines));\n      for (int lineNumber = 0; lineNumber &lt; countLessOne; lineNumber++) {\n        if (contents.lineIsBlank(lineNumber) &amp;&amp; contents.lineIsBlank(lineNumber+1)) {\n          log(lineNumber+2, \"Multiple empty lines.\");\n          while (lineNumber &lt; countLessOne &amp;&amp; contents.lineIsBlank(lineNumber)) {\n            lineNumber++;\n          }\n        }\n      }\n    }\n  }\n\n}<\/code><\/pre>\n<!-- \/wp:code -->\n<h4>AbstractCheck<\/h4>\nWant even more advanced syntax check? We can extend AbstractCheck and implement getDefaultTokens() and visitToken(). It shows error messages if it finds any LABELED_STAT token.\n\n<!-- wp:code {\"language\":\"java\",\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>package com.wealthfront.test.check;\n\nimport com.puppycrawl.tools.checkstyle.api.DetailAST;\nimport com.puppycrawl.tools.checkstyle.api.TokenTypes;\n\npublic class NoLabelStatementCheck extends AbstractCheck {\n\n  @Override\n  public int[] getRequiredTokens() {\n    return getDefaultTokens();\n  }\n\n  @Override\n  public int[] getDefaultTokens() {\n    return getAcceptableTokens();\n  }\n\n  @Override\n  public int[] getAcceptableTokens() {\n    return new int[] { TokenTypes.LABELED_STAT };\n  }\n\n  @Override\n  public void visitToken(DetailAST ast) {\n    switch (ast.getType()) {\n      case TokenTypes.LABELED_STAT: {\n        visitLabelledStatement(ast);\n        break;\n      }\n      default: {\n        final String exceptionMsg = \"Unexpected token type: \" + ast.getText();\n        throw new IllegalArgumentException(exceptionMsg);\n      }\n    }\n  }\n\n  protected void visitLabelledStatement(DetailAST ast) {\n    log(ast.getLineNo(), ast.getColumnNo(), String.format(\"Labeled statement is not allowed\"));\n  }\n}<\/code><\/pre>\n<!-- \/wp:code -->\n<p id=\"CheckstyleatWealthfront-Conclusion\">By using Checkstyle we can check the code at the earliest possible time and detect some patterns that may cause bigger troubles later. It can do more than just coding style check, for example, it can detect the classes with external operations which should not appear in the database transactions, or remind us to call mockery.assertIsSatisfied() if we use mockery in test cases. Welcome to add more customized checks and find the potential issues as early as possible.<\/p>","excerpt":"At Wealthfront we\u00a0use Checkstyle to enforce some coding standards, also to detect bad practice at the early stage. In this blog post, we will describe how to configure Checkstyle and how to customize that for our needs. How to configure Checkstyle? There are three components working together to automate all these checks: checkstyle: the tool... <a class=\"moretag\" href=\"https:\/\/eng.wealthfront.com\/2016\/10\/26\/checkstyle-at-wealthfront\/\" aria-label=\"Read more about Checkstyle at Wealthfront\">Read more<\/a>","date":"2016-10-26 18:20:17","author":"Yen-Ming Lee","categories":["wealthfront engineering"],"tags":[]},{"id":1071,"title":"Unit Testing for Sass with Sassaby","permalink":"https:\/\/eng.wealthfront.com\/2015\/07\/09\/unit-testing-for-sass-with-sassaby\/","content":"At Wealthfront we use Sass to write all of our CSS stylesheets. Sass is a powerful CSS pre-processor that allows you to leverage features common in programming languages, but are absent from native CSS. Using Sass variables, conditionals, loops, and functions, you can write extensible CSS that is easier to maintain across a large front-end codebase.\n\nAs you can tell by countless posts on this blog, we take testing very seriously at Wealthfront. As a precursor to leveraging all of the Sass features, especially its repeatable functions, we needed a way to ensure that our Sass was adequately tested. This led us to develop and open-source <a href=\"https:\/\/github.com\/wealthfront\/sassaby\" target=\"_blank\" rel=\"noopener\">Sassaby<\/a>, a unit testing library for Sass.\n\nIn this blog post I'm going to detail a bit of the thought process that led us here and then use some examples from Wealthfront's codebase to show some of features of Sassaby.\n<h2>What Brought us Here<\/h2>\nOur needs to test Sass are similar to the needs of any testing library:\n<ol>\n\t<li>Tests should be easy to write in a syntax that is familiar to its target audience.<\/li>\n\t<li>Assertions should be available for all features that should be tested.<\/li>\n\t<li>Tests should easily be integrated into our existing build system.<\/li>\n\t<li>Tests should run quickly in that build system.<\/li>\n<\/ol>\nWhile there are a few libraries already available for testing Sass, they are also written in Sass, which broke our first need. Sass is a great language for what it intends to be \u2014 a CSS pre-processor. Its syntax is not optimized for writing other types of scripts, such as testing libraries. Also, since it has to be compiled to CSS, writing testing code in Sass also had the downside of having to compile down to a file with the test results (which then had to be parsed). We preferred a testing library that would log the failing tests and immediately exit the build process.\n\nWe quickly settled on JavaScript as the language of choice for Sassaby. Writing this library with server-side JavaScript allowed us to:\n<ol>\n\t<li>Integrate with our existing Node testing framework, Mocha.<\/li>\n\t<li>Use any of the available Node assertion libraries.<\/li>\n\t<li>Easily integrate with our build system (because we already have Node tests running).<\/li>\n\t<li>Use <a href=\"https:\/\/www.npmjs.com\/package\/node-sass\" target=\"_blank\" rel=\"noopener\">node-sass<\/a> for compilation. This package is the Node wrapper on the <a href=\"https:\/\/github.com\/sass\/libsass\" target=\"_blank\" rel=\"noopener\">libsass<\/a> library (significantly faster than normal Ruby Sass compilation).<\/li>\n\t<li>Use the excellent <a href=\"https:\/\/www.npmjs.com\/package\/css\" target=\"_blank\" rel=\"noopener\">Rework CSS<\/a> package for parsing compiled CSS into JSON.<\/li>\n<\/ol>\n<h2>Sassaby<\/h2>\nWe built Sassaby to be testing framework agnostic. It will work with whichever Node testing library you are currently using. For the purposes of the examples in this blog post, however, we will be using Mocha. Setting up Sassaby is easy, as you just tell it which file you want to test:\n\n<!-- wp:code {\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>'use strict';\n\nvar path = require('path');\nvar Sassaby = require('sassaby');\n\ndescribe('sample.scss', function() {\n  var sassaby;\n  \n  beforeEach(function() {\n    sassaby = new Sassaby(path.resolve(__dirname, 'sample.scss'));\n  });\n});<\/code><\/pre>\n<!-- \/wp:code -->\n\nSassaby breaks down its features into the main repeatable parts of Sass: mixins, functions, and imports.\n<h3>Mixins<\/h3>\nMixins allow you to return a repeated CSS rule declaration or set of CSS styles into your stylesheet. Sassaby has two categories of mixins: included mixins (returns a set of CSS styles) and standalone mixins (returns a full CSS rule declaration). Here is one of the standalone mixins from Wealthfront's grid:\n\n<!-- wp:code {\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>@mixin make-align-center($label) {\n  .align-center-#{$label} {\n    justify-content: center;\n  }\n}<\/code><\/pre>\n<!-- \/wp:code -->\n\nThis mixin has two main features that we would want to test. It interpolates the given argument into a class selector and gives it a declaration for center aligning items in the grid. We can test this with Sassaby by setting up the standalone mixin, calling it with an argument, and using some of the built-in assertions.\n\n<!-- wp:code {\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>describe('make-align-center', function() {\n  it('should create the correct class', function() {\n    sassaby.standaloneMixin('make-align-center').calledWith('md').createsSelector('.align-center-md');\n  });\n  \n  it('should make the correct declaration', function() {\n    sassaby.standaloneMixin('make-align-center').calledWith('md').declares('justify-content', 'center');\n  });\n});<\/code><\/pre>\n<!-- \/wp:code -->\n\nSassaby's <code>calledWithArgs<\/code> function takes the specified file, the mixin, and its arguments and compiles it to the resulting CSS and an <a href=\"http:\/\/iamdustan.com\/reworkcss_ast_explorer\/\" target=\"_blank\" rel=\"noopener\">AST<\/a>. Doing the CSS compilation at this step allows for each mixin to be tested in isolation and with different arguments as needed.\n\nStandalone mixins are different than included mixins, which return only CSS style declarations so that they can be called inside an already defined rule set. Here is one of our included mixins that adds browser prefixes to the CSS filter declaration:\n\n<!-- wp:code {\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>@mixin filter-grayscale($percent) {\n  -webkit-filter: grayscale($percent);\n  -moz-filter: grayscale($percent);\n  -ms-filter: grayscale($percent);\n  -o-filter: grayscale($percent);\n  filter: grayscale($percent);\n}<\/code><\/pre>\n<!-- \/wp:code -->\n\nThe testing interface for included mixins is very similar. For example we would test this mixin like this:\n\n<!-- wp:code {\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>describe('filter-grayscale', function() {\n  var mixin;\n  var called;\n  \n  beforeEach(function() {\n    mixin = sassaby.includedMixin('filter-grayscale');\n    called = mixin.calledWith('50%');\n  });\n  \n  it('should make the correct declarations', function() {\n    called.declares('-webkit-filter', 'grayscale(50%)');\n    called.declares('-moz-filter', 'grayscale(50%)');\n    called.declares('-ms-filter', 'grayscale(50%)');\n    called.declares('-o-filter', 'grayscale(50%)');\n    called.declares('filter', 'grayscale(50%)');\n  });\n});<\/code><\/pre>\n<!-- \/wp:code -->\n<h3>Functions<\/h3>\nFunctions are similar to mixins in Sass, but do not deal with actual CSS style selectors or rules. They are commonly used for unit conversions, such as this one that converts pixels to rems:\n\n<!-- wp:code {\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>@function rems($pxsize, $rembase) {\n  @return ($pxsize\/$rembase)+rem;\n}<\/code><\/pre>\n<!-- \/wp:code -->\n\nSassaby supports a function testing interface similar to its mixin interface, but with different assertions. For this function, we want to test that the division is done correctly and that the correct unit is appended to the end. We can accomplish both of these goals with the <code>equals<\/code> assertion, but the purposes of this example I have also showed the <code>doesnotEqual<\/code> assertion.\n\n<!-- wp:code {\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>describe('rems', function() {\n  it('convert to px units to rem units', function() {\n    sassaby.func('rems').calledWithArgs('32px', '16px').equals('2rem');\n  });\n\n  it('has the correct output unit', function() {\n    sassaby.func('rems').calledWithArgs('32px', '16px').doesNotEqual('2em');\n  });\n});<\/code><\/pre>\n<!-- \/wp:code -->\n<h3>Imports<\/h3>\nThe final testable feature in Sassaby is imports, which allow you break out variables, mixins, and styles into a more organized file structure. Let's say our main file looks like this:\n\n<!-- wp:code {\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>@import 'variables';\n@import 'mixins';\n@import 'layout';<\/code><\/pre>\n<!-- \/wp:code -->\n\nWe obviously would want to test that these files are imported. Sassaby allows this through this interface:\n\n<!-- wp:code {\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>describe('imports', function() {\n  it('should import variables', function() {\n    sassaby.imports('variables');\n  });\n  \n  it('should import mixins', function() {\n    sassaby.imports('mixins');\n  });\n  \n  it('should import layout', function() {\n    sassaby.imports('layout');\n  });\n});<\/code><\/pre>\n<!-- \/wp:code -->\n<h2>Bringing it all Together<\/h2>\nOne thing that we've noticed by starting to test our Sass mixins is that it has forced us to write better single responsibility mixins. Here is one of our old mixins from our grid that handles the creation of rules for reordering columns:\n\n<!-- wp:code {\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>@mixin make-specific-alignments($label) {\n  @for $i from 1 through ($grid-columns) {\n    .order-#{$label}-#{$i} {\n      order: $width - 1;\n    }\n  }\n}<\/code><\/pre>\n<!-- \/wp:code -->\n\nTrying to test this would be a bit too complicated. We would have to test that the loop is creating a new class for each column, that the label is interpolated correctly, and that the rule declaration is one less than the column number. It would be much easier to break it out into two single responsibility mixins:\n\n<!-- wp:code {\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>@mixin make-order($label, $width) {\n  .order-#{$label}-#{$width} {\n    order: $width - 1;\n  }\n}\n\n@mixin make-specific-alignments($label) {\n  @for $i from 1 through ($grid-columns) {\n    @include make-order($label, $i)\n  }\n}<\/code><\/pre>\n<!-- \/wp:code -->\n\nDoing this will allow us to test both of these mixins in isolation. Also, notice how these mixins rely on an externally defined variable, <code>$grid-columns<\/code>. Sassaby provides a way to stub in these external dependencies, as shown with the below tests for the broken out mixins:\n\n<!-- wp:code {\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>describe('_grid.scss', function() {\n  var gridColumns = 12;\n  var mixin;\n  var compiled;\n  var sassaby;\n  \n  beforeEach(function() {\n    sassaby = new Sassaby('_grid.scss', {\n      variables: {\n        'grid-columns': gridColumns\n      }\n    });\n  });\n\n  describe('make-order', function() {\n    beforeEach(function() {\n      mixin = sassaby.standaloneMixin('make-order');\n      compiled = mixin.calledWithArgs('lg', 6);\n    });\n  \n    it('should create the correct selector', function() {\n      compiled.createsSelector('.order-lg-6');\n    });\n  \n    it('should make the correct declaration', function() {\n      compiled.declares('order', '5');\n    });\n  });\n  \n  describe('make-specific-alignments', function() {\n    beforeEach(function() {\n      mixin = sassaby.standaloneMixin('make-specific-alignments');\n      compiled = mixin.calledWithArgs('lg');\n    });\n  \n    it('should call the correct mixins', function() {\n      for(var i = 1; i &lt;= gridColumns; i++) {\n        compiled.calls('make-order(lg, ' + i + ')');\n      }\n    });\n  });\n});<\/code><\/pre>\n<!-- \/wp:code -->\n\nWe hope that this has been a good guide to writing more coherent CSS with Sass and testing it with Sassaby. The assertions used in these examples are only a sample of what is available, and we encourage you to check out the full documentation and download the library on <a title=\"Sassaby on npm\" href=\"https:\/\/www.npmjs.com\/package\/sassaby\">npm<\/a>. We also take pull requests on <a title=\"Sassaby on GitHub\" href=\"https:\/\/github.com\/wealthfront\/sassaby\">GitHub<\/a>.","excerpt":"At Wealthfront we use Sass to write all of our CSS stylesheets. Sass is a powerful CSS pre-processor that allows you to leverage features common in programming languages, but are absent from native CSS. Using Sass variables, conditionals, loops, and functions, you can write extensible CSS that is easier to maintain across a large front-end... <a class=\"moretag\" href=\"https:\/\/eng.wealthfront.com\/2015\/07\/09\/unit-testing-for-sass-with-sassaby\/\" aria-label=\"Read more about Unit Testing for Sass with Sassaby\">Read more<\/a>","date":"2015-07-09 10:15:00","author":"Ryan Bahniuk","categories":["CSS","javascript","node","npm","sass","sass testing","scss","testing","unit testing"],"tags":[]},{"id":1086,"title":"iOS UI Testing","permalink":"https:\/\/eng.wealthfront.com\/2014\/09\/16\/ios-ui-testing\/","content":"I was initially attracted to Wealthfront by <a href=\"https:\/\/www.wealthfront.com\/who-we-are\">its great business model<\/a> and <a href=\"http:\/\/eng.wealthfront.com\/2013\/11\/how-to-find-great-engineering-culture.html\">engineering driven culture<\/a>. But after visiting the office, I was convinced that I belonged to Wealthfront because of the strong emphasis on <a href=\"http:\/\/eng.wealthfront.com\/search\/label\/testing\">test driven development (TDD)<\/a>, <a href=\"http:\/\/eng.wealthfront.com\/search\/label\/continuous%20deployment\">continuous integration, and rapid deployment<\/a>. Furthermore, <a href=\"http:\/\/eng.wealthfront.com\/2014\/03\/ios-development-at-wealthfront.html\">a solid foundation had already been laid for the iOS initiatives<\/a> with a great test suite and a stable continuous deployment environment. TDD in Objective-C is something I always wanted to do, but sadly it is not a common practice in our community for a variety of reasons. Everyone agrees that unit testing is a good idea, but many developers shy away because they believe it is too hard.\n\nFor example a common misconception about iOS applications is that they largely involve UI code which is difficult to test in unit tests.\n\nAfter working at Wealthfront for a few months I now know this is untrue. I am very glad that I made the right decision to join a great team dedicated to leading in this space. Of course there was some initial time investment to get used to the rigor of working with TDD and continuous integration. But after a short while, my productivity and code quality improved significantly. Moreover, I gained a much deeper understanding of how to anticipate the requirements of a class or method to allow much faster refactoring and iterative improvement. Our code base has grown significantly and we have pushed many major updates after our first launch without sacrificing code quality. We have also achieved over 93% code coverage with our test suite which we continue to improve.\n\nI would like to share an example to demonstrate that UI testing is actually not that difficult. By decomposing the application into reusable, testable components, we can ensure the stability of the system without relying on large, cumbersome integration tests. To do this, we need to isolate the dependencies of individual components and leverage the compiler to guarantee a consistent API contract between these components. Finally, we can leverage mock objects or fake data as necessary to imitate the expected behavior of these dependencies. This approach allows us to eliminate test variability introduced by external dependencies such as the network, database, and UI animation. At Wealthfront, we use the Objective-C mock object framework <a href=\"http:\/\/ocmock.org\/\">OCMock<\/a> (which is equivalent to JMock in Java) to facilitate this decomposition.\n<h2>WFPinView onboarding page<\/h2>\nThe example we'll look at today is from our PIN on-boarding flow. When a new user launches the Wealthfront application for the first time they are shown an informative page (called <code>WFPinView<\/code> in this example) about our PIN feature. If the user wants to set up a PIN, they can either tap the right side \u201cSET PIN\u201d button or swipe the lock to the right until it touches the \u201cSET PIN\u201d button. Additionally the bar to the left of the lock changes color to visually reinforce the user\u2019s choice. Alternatively the user can swipe the lock to the left or tap the left button to choose \"NOT NOW\" and dismiss the view.\n<div style=\"clear: both; text-align: center;\"><a style=\"margin-left: 1em; margin-right: 1em;\" href=\"https:\/\/eng.wealthfront.com\/wp-content\/uploads\/2014\/09\/PIN_selection.jpg\"><img src=\"https:\/\/eng.wealthfront.com\/wp-content\/uploads\/2014\/09\/PIN_selection.jpg\" alt=\"\" width=\"400\" height=\"307\" border=\"0\" \/><\/a><\/div>\nThere are a few things we want to do to test this UI:\n<ol>\n\t<li>ensure each view is initially positioned at the correct location<\/li>\n\t<li>ensure all labels, colors, and text match the design specs<\/li>\n\t<li>confirm that all controls and gesture recognizers are properly configured for our expected user interactions<\/li>\n\t<li>ensure smooth animation for position and color changes in response to user interaction<\/li>\n<\/ol>\n<h2>Building the view with TDD<\/h2>\nThe first two steps are quite straightforward. By following TDD, we first layout the expected subviews' position, color, and text in our test code according to our design specifications. Then we programmatically build the view. At the end, we just need to use <code>XCTAssert<\/code> to confirm the subviews such as labels, buttons and images to have been positioned and the properties of the subviews are as expected:\n\n<!-- wp:code {\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>- (void)testSubviews {  \n  WFPinView *v = [[WFPinView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 320.0f, 568.0f)];\n  [v layoutSubviews];\n  CGRect expectedRect = CGRectMake((25.0f, 0.0f, 271.0f, 568.0f));\n  XCTAssertTrue(CGRectEqualToRect(expectedRect, v.phoneBg.frame), @\"Wrong phoneBg frame\");\n  XCTAssertTrue(v.phoneBg.superview == v, @\"Wrong superview\");\n  XCTAssertTrue([v.setPinButton isKindOfClass:[UIButton class]], @\"Wrong class\");\n  expectedRect = CGRectMake(261.0f, 217.0f, 47.0f, 47.0f);\n  XCTAssertTrue(CGRectEqualToRect (expectedRect, (, v.setPinButton.frame);\n...\n}<\/code><\/pre>\n<!-- \/wp:code -->\n\nIn the above test, we confirm the following:\n<ul>\n\t<li>All of the subviews are positioned correctly (for simplicity, only a few lines of code are shown)<\/li>\n\t<li>They are in the right view hierarchy<\/li>\n\t<li>They are the correct type and their text and color are all as expected<\/li>\n<\/ul>\nWhen the test passes, we know the view is laid out correctly. Later if our design team wants to change something in <code>WFPinView<\/code>, one or more of the tests would fail and we will need to update the tests to make them pass again. This gives us a great built-in control to ensure any changes we make are the desired changes. Once we are certain the view is constructed correctly, we can start thinking about how the user interacts with the view.\n<h2>Validating user interaction events<\/h2>\nWe need to make sure that user's interaction with views are handled correctly. On iOS, user interactions are dispatched by associating a target and action with a control (e.g. UIButton). At first glance, it seems there is no way for us to validate the button's target or action. Additionally it is unclear how to trigger these actions from our test. For example, by just looking at the <code>UIButton<\/code> class documentation there is no obvious method we can use to accomplish this. Fortunately, <code>UIButton<\/code> is a <code>UIControl<\/code> and when we look at <code>UIControl<\/code> there are a number of APIs available for us to use as shown in the following piece of test code:\n\n<!-- wp:code {\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>- (void)testSetPinButton {\n  NSArray *actions = [vc.pinView.setPinButton actionsForTarget:vc\n                                               forControlEvent:UIControlEventTouchUpInside];\n                                               \n  XCTAssertTrue(actions.count == 1, @\"Should only be one: %@\", actions);\n  NSString *selectorName =  [actions lastObject];\n  XCTAssertTrue([selectorName isEqualToString:@\"setPinTapped:\"], @\"Wrong selector name\");\n  \n  id partialMockVC = [OCMockObject partialMockForObject:vc];\n  [[partialMockVC expect] setPinTapped:vc.pinView.setPinButton];\n  [vc.pinView.setPinButton sendActionsForControlEvents:UIControlEventTouchUpInside];\n}<\/code><\/pre>\n<!-- \/wp:code -->\n\nThe <code>pinView<\/code> is controlled by its view controller (vc). The first line in this test is to extract all of <code>setPinButton<\/code>'s actions targeted to vc. We can then call <code>XCTAssert<\/code> to confirm its action selector is indeed the <code>-setPinTapped: <\/code> method by matching the <code>selectorName<\/code>.\nThen we partially mock the view controller and set our expectation that the <code>-setPinTapped: <\/code> method would be called if and only if the <code>setPinButton<\/code> is tapped. Here the key is to fake the user tap event by calling <code>UIControl<\/code>\u2019s <code>-sendActionsForControlEvents:<\/code> method.\n\nSimilarly, other UIControl subclasses such as <code>UIPageControl<\/code>, <code>UISegmentedControl<\/code>, <code>UITextField<\/code> can use a similar scheme to confirm the target-action configuration is as expected. Next we need to validate the animations for this view.\n<h2>Unit testing animations with andDo:<\/h2>\nOne reason iOS developers shy away from unit testing is that UI is one of the major focuses of development. It is very typical for an iOS application to have animations. Some of them are pretty simple -- views that appear, disappear, or change color through a brief animated sequence. Other animations are more elaborate, involving groups of views and possibly completion blocks.\n\nLet\u2019s take a look at a simplified version of one of our animations in the <code>WFPinView<\/code> class:\n\n<!-- wp:code {\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>- (void)animateLockWithGestureRecognizer:(UIPanGestureRecognizer *)gr completion:(void (^)(void))completionBlock {\n  [self removeGestureRecognizer:gr];\n\n  \/\/calculate animationDuration based on gr position and velocity\n\n  [UIView animateWithDuration:animationDuration animations:^{\n    \/\/some calculation logic to determine a view\u2019s location\n\n    [self updateSliderColor:offset];\n  } completion:^(BOOL finished) {\n    if (completionBlock) {\n      completionBlock();\n    }\n  }];\n}<\/code><\/pre>\n<!-- \/wp:code -->\n\nThis code adjusts the position of a <code>UIView<\/code> object (the lock) based on the velocity and distance of a finger movement that is captured by a <code>UIPanGestureRecognizer<\/code>. At the same time, another view (the slider view) adjusts its color accordingly. After the animation finishes, a completionBlock is called to do further work.\n\nEven in this quite simple animation, we have many things to test to make sure it behaves as expected:\n<ul>\n\t<li>We need to confirm that the expected methods (e.g. <code>+[UIView animateWithDuration:delay:options:animations:completion:]<\/code>) are called with the proper parameters<\/li>\n\t<li>We need to be sure the view moves to where we want it to be and the color of the slider view is changed correctly<\/li>\n\t<li>We need to be certain that if there is a completionBlock, the block is called<\/li>\n<\/ul>\nThe following is part of the test code:\n\n<!-- wp:code {\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>- (void) testAnimation {\n  id mockGestureRecognizer = [OCMockObject mockForClass:[UIPanGestureRecognizer class]];\n  CGPoint expectedPoint = CGPointMake(-10.0f, 20.0f); \/\/velocity is in CGPoint unit\n  [(UIPanGestureRecognizer *)[[mockGestureRecognizer expect] andReturnValue:OCMOCK_VALUE(expectedPoint)] velocityInView:v];\n  \n  id partialMockPinView = [OCMockObject partialMockForObject:v];\n  [[partialMockPinView expect] updateSliderColor:delta];\n\n  NSTimeInterval expectedAnimationDuration = fabs(expectedPoint.x) * 0.002 * 0.2;\n  id mockView = [OCMockObject mockForClass:[UIView class]];\n  [[[mockView expect] andDo:^(NSInvocation *i) {\n    void (^animation)(void) = nil;\n    void (^completion)(void) = nil;\n    [i getArgument:&amp;animation atIndex:3];\n    [i getArgument:&amp;completion atIndex:4];\n    animation();\n    if(completion) {\n       completion();\n    }\n    }] animateWithDuration:expectedAnimationDuration\n                animations:[OCMArg isNotNil]\n                completion:OCMOCK_ANY];\n\n  __block BOOL finished = NO;\n  [v animateLockWithGestureRecognizer:mockGestureRecognizer \n                           completion:^{\n                                        finished = YES;\n                                      }];\n                                      \n  XCTAssertTrue(finished == YES, @\"Should finished\");\n  XCTAssertTrue(CGRectEqualToRect(expectedRect, v.lockImageView.frame), @\u201dWrong frame\u201d);\n\n  [mockGestureRecognizer verify];\n  [partialMockPinView verify]; [partialMockPinView stopMocking];\n  [mockView verify]; [mockView stopMocking];\n}<\/code><\/pre>\n<!-- \/wp:code -->\n<ul>\n\t<li><b>Lines 2-4:<\/b> We set up a <code>mockGestureRecognizier<\/code> to be used by <code>WFPINView<\/code>\u2019s <code>-animateLockWithGestureRecognizer:completion:<\/code> method to calculate the animation duration and lock offset.<\/li>\n\t<li><b>Lines 6-7:<\/b> We use <code>partialMockView<\/code> to identify the method that should be called (<code>-updateSliderColor:<\/code>) with the expected delta value. If the expected value calculated in the test is different from the output in <code>WFPinVew<\/code>, an assertion error is generated.<\/li>\n\t<li><b>Lines 9-22:<\/b> We then mock <code>+[UIView animationWithDuration:animations:completion:]<\/code> method so that the <code>mockView<\/code> is used to confirm the calling parameters matches what we expect.\n<ul>\n\t<li>If the real animation duration is different from <code>expectedAnimationDuration<\/code>, the test will fail.<\/li>\n\t<li>Use <code>andDo:<\/code> to set return values and access call parameters (e.g. the <code>animation()<\/code> and <code>completion()<\/code> blocks.<\/li>\n\t<li>We expect at least the <code>animation()<\/code> block to be supplied so we can use <code>[OCMArg isNotNil]<\/code> to check that it is present. Alternatively, we don\u2019t always expect to have a <code>completion()<\/code> block, so we use <code>OCMOCK_ANY<\/code> to signify this constraint.<\/li>\n<\/ul>\n<\/li>\n\t<li><b>Lines 14-15:<\/b> We use <code>-getArgument:atIndex:<\/code> from <code>NSInvocation<\/code> to get the calling method\u2019s parameters we are interested in. Since we have already confirmed that <code>animationDuration<\/code> is as expected, we simply execute <code>animation()<\/code> immediately and if there is a <code>completion()<\/code> block, we execute it as well.<\/li>\n\t<li><b>Lines 24-28:<\/b> We setup a completion block to change a BOOL value inside the completion block to confirm it is called correctly.<\/li>\n\t<li><b>Line 30:<\/b> We confirm the completion block is actually executed by checking the BOOL value is changed from NO to YES. This would only happen if the completion block is executed.<\/li>\n\t<li><b>Line 31:<\/b> We further ensure that the animated view is located at its final expected position after animation block executes.<\/li>\n\t<li><b>Lines 33-35:<\/b> We call <code>-verify<\/code> on the mocked objects and classes to ensure they were called as expected.<\/li>\n<\/ul>\n<h2>Final thoughts<\/h2>\nUnit testing in iOS is not always easy, but after a few months of actively writing tests, it starts to become my second nature. With the development of many great tools such as OCMock, Kiwi, Xcode server and more evangelism from Apple, it is getting a lot easier. As Wealthfront continues its hyper-growth our new hires will onboard into this environment and quickly start contributing to our ongoing development. With this infrastructure in place we are confident that we can rapidly add new features and scale our code base while ensuring the quality of our application meets our standards.","excerpt":"I was initially attracted to Wealthfront by its great business model and engineering driven culture. But after visiting the office, I was convinced that I belonged to Wealthfront because of the strong emphasis on test driven development (TDD), continuous integration, and rapid deployment. Furthermore, a solid foundation had already been laid for the iOS initiatives... <a class=\"moretag\" href=\"https:\/\/eng.wealthfront.com\/2014\/09\/16\/ios-ui-testing\/\" aria-label=\"Read more about iOS UI Testing\">Read more<\/a>","date":"2014-09-16 08:45:00","author":"Daniel Zheng","categories":["ios","mobile","objective-c","test-driven development","testing","ui"],"tags":[]},{"id":1108,"title":"iOS Development at Wealthfront","permalink":"https:\/\/eng.wealthfront.com\/2014\/03\/12\/ios-development-at-wealthfront\/","content":"At Wealthfront everything we build is designed to move quickly. For example, it is common for new hires to deploy new code to production on their first day. Our <a href=\"http:\/\/eng.wealthfront.com\/search\/label\/continuous%20deployment\" target=\"_blank\" rel=\"noopener\">continuous integration and deployment environment<\/a> are designed so that if the tests pass we ship. This, of course, is actually quite normal for a Silicon Valley startup with a dedicated team of talented engineers. We believe that's just how things should work.\n\nUnfortunately the same cannot be said for iOS development in general where that cadence is, strictly speaking, atypical. App Store friction aside most organizations are challenged to ship an update to their iOS app every month, let alone every week or every day. Yet that's exactly what we can do here. In the time it took Apple to approve our first release, we shipped four significant updates to the app internally.\n\nLet's take a look at how we've achieved such rapid cadence with Wealthfront for iOS.\n<h2>The Wealthfront Way<\/h2>\nOur dedication to effective continuous integration, test driven development and painless deployment were a big factor in my decision to join the company. I was actually quite excited to apply the same paradigms and methodology to our mobile initiatives. Using the existing infrastructure as a model I identified several important goals:\n<ul>\n\t<li>High quality tests should be easy to write<\/li>\n\t<li>Tests should fail when something breaks, and pass when nothing is broken<\/li>\n\t<li>Continuous Integration should create \"production\" ready builds<\/li>\n\t<li>Establish a \"master\" stable development cycle where we can release immediately without periods of \"stabilization\" or \"convergence\"<\/li>\n<\/ul>\n<h2>Write Great Tests<\/h2>\nThis might sound obvious, the better the tests are the better the final product will be. However in a high velocity development environment the tests have to be as easy as possible to write. At Wealthfront we use a number of tools to facilitate rapid test iteration like OCMock and CoreData. Both provide elegant and scalable solutions to the core challenges of creating a scalable test framework.\n\n<a href=\"http:\/\/ocmock.org\/\" target=\"_blank\" rel=\"noopener\">OCMock<\/a> is the Objective C equivalent of JMock. It's a mock object framework that allows engineers to separate the concerns of individual tests and ensure that each test is only executing the specific code under test. Leveraging a framework like OCMock in our continuous integration test suite greatly reduces the number of spurious or accidental failures by ensuring that tests are very tightly scoped. The natural corollary is that test results are consistent and \"believable\", so the build server's emails are never ignored or written off as \"just having a bad day\".\n\nOur app uses Core Data under the covers to cache and organize data from our API server. Electing to use Core Data was a conscious decision based partly on its ability to be used in a test framework. At Wealthfront we believe that data driven testing is essential to our ability to validate our code. Therefore whatever persistence framework we chose had to integrate seamlessly with our test suite.\n\nWith <a href=\"https:\/\/developer.apple.com\/library\/mac\/documentation\/cocoa\/conceptual\/coredata\/cdProgrammingGuide.html\" target=\"_blank\" rel=\"noopener\">Core Data<\/a> data driven tests are actually easier to write than many alternative solutions because the test can simply configure a managed object context with the desired data and pass that context to the object under test. This type of fixture facilitates easy case enumeration (from common to boundary) with reliable and consistent results.\n<h2>Integrate Continuously<\/h2>\nContinuous integration with iOS has, until recently, been rather challenging. The Internet is full of workarounds and scripts for making solutions like Jenkins play nicely with Xcode and the iOS Simulator. But with the release of iOS 7 and OSX 10.9 Apple included a native way to support continuous integration, Xcode Server. We use Xcode server to pull changes from our Git repository and run them against the simulator and real hardware.\n\nAs with the other projects at Wealthfront our build server is at the heart of our continuous integration suite. After a pull request is approved and merged to the master branch of our git repository Xcode Server runs four different bots:\n<ul>\n\t<li>Our unit test suite against the app store build<\/li>\n\t<li>Our unit test suite against the internal build<\/li>\n\t<li>A device ready app store build<\/li>\n\t<li>A device ready internal build<\/li>\n<\/ul>\nThe unit test bots give us coverage over our production and internal builds while the other two build production ready archives that can be deployed to devices.\n<h2>Stable Continuous Deployment<\/h2>\nWith this infrastructure in place we have been able to reliably hit a steady one week cadence, where improvements and even smaller new features are released at the end of each week to our employees. At the time of this writing our test framework has ~400 unit tests which evaluate everything from our parsing \/ caching code to custom implementations of -drawRect:. The tests take ~6-7 seconds to run per instance; the entire suite takes less than a minute on all the flavors of the iOS simulator and a few choice bits of real hardware we keep hooked up to the build server.\n\nWe've also been able to maintain a very high quality bar. For example our first internal beta had zero reported bugs, an achievement we have since repeated many times. Of course that doesn't mean there aren't any bugs in our code, they were just very difficult to find and reproduce, as they should be with well tested code.\n\nSimilar to our other projects we've achieved a master stable release cycle where changes that are successfully built by the build server after being merged to master can be shipped to the App Store. Of course, that doesn't mean we ship every passing build to the App Store. But we have the infrastructure and tools in place to know that we could if we had to.\n\nIn all we are quite happy with we have achieved thus far and we'll be sharing a lot more about our test infrastructure and iOS development at Wealthfront in general in the coming weeks. Until then please let us know your thoughts and questions in the comments.","excerpt":"At Wealthfront everything we build is designed to move quickly. For example, it is common for new hires to deploy new code to production on their first day. Our continuous integration and deployment environment are designed so that if the tests pass we ship. This, of course, is actually quite normal for a Silicon Valley... <a class=\"moretag\" href=\"https:\/\/eng.wealthfront.com\/2014\/03\/12\/ios-development-at-wealthfront\/\" aria-label=\"Read more about iOS Development at Wealthfront\">Read more<\/a>","date":"2014-03-12 12:59:00","author":"Nick Gillett","categories":["wealthfront engineering"],"tags":[]},{"id":1136,"title":"Reactive Charts with D3 and Reactive.js","permalink":"https:\/\/eng.wealthfront.com\/2013\/04\/18\/reactive-charts-with-d3-and-reactivejs\/","content":"<span style=\"color: #999999;\"><i>New to Reactive.js? <a href=\"http:\/\/eng.wealthfront.com\/2013\/04\/reactivejs-functional-reactive.html\" target=\"_blank\" rel=\"noopener\">Check out last week's introduction<\/a>.<\/i><\/span>\n\nAll visualizations are ultimately a composition of smaller elements; data sets, scales, individual lines, and labels to name a few. Those elements relate to each other in different ways, and to manage their interdependency we typically find ourselves writing a master \"render\" method \u2014 something that can take new data and re-draw the visualization in its entirety, imperatively recalculating each component.\n\nAt <a href=\"https:\/\/www.wealthfront.com\/\" target=\"_blank\" rel=\"noopener\">Wealthfront<\/a> we've been using Reactive.js to do things a little differently. We describe our visualizations as a flow of information, not as an imperative set of rendering steps. To illustrate this, we'll build up a simple bar chart that shows how Reactive.js can change the way you write, and interact with your visualizations \u2014\u00a0hopefully for the better! When we're done, our humble chart will look a little something like this:\n<div style=\"clear: both; text-align: center;\"><a style=\"margin-left: 1em; margin-right: 1em;\" href=\"http:\/\/1.bp.blogspot.com\/-CtgOnptFfxM\/UW8syg0YmwI\/AAAAAAAAAAQ\/IC53tTH5yJ4\/s1600\/bar.png\"><img src=\"http:\/\/1.bp.blogspot.com\/-CtgOnptFfxM\/UW8syg0YmwI\/AAAAAAAAAAQ\/IC53tTH5yJ4\/s1600\/bar.png\" alt=\"\" border=\"0\" \/><\/a><\/div>\nFirst, we need to declare each component of our chart and how those components relate to each other, so let's decompose our chart into the values that describe it.\n<h2>Width and Height<\/h2>\n<div style=\"clear: both; text-align: center;\"><a style=\"margin-left: 1em; margin-right: 1em;\" href=\"http:\/\/3.bp.blogspot.com\/-LyfM_SKAyZU\/UW8s3fXL4WI\/AAAAAAAAAAY\/XGHzrxbssOU\/s1600\/bar__0000_Dimensions.png\"><img src=\"http:\/\/3.bp.blogspot.com\/-LyfM_SKAyZU\/UW8s3fXL4WI\/AAAAAAAAAAY\/XGHzrxbssOU\/s1600\/bar__0000_Dimensions.png\" alt=\"\" border=\"0\" \/><\/a><\/div>\nWidth and Height represent the width and height of our chart in pixels. Since they're just simple values, we'll represent them with <code>$R.state()<\/code>. Remember that $R.state() just returns a reactive function that lets you store and retrieve a value. \u00a0We'll set the width and height to 200px by default.\n\n<!-- wp:code {\"language\":\"javascript\",\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>function BarChart(svg) {\n  this.width = $R.state(200);\n  this.height = $R.state(200);\n}<\/code><\/pre>\n<!-- \/wp:code -->\n<h2>Data<\/h2>\n<h4><span style=\"font-family: Courier New, Courier, monospace;\">\"[{name:foo, value:10}, {name:bar, value:20}]\"<\/span><\/h4>\n<div><span style=\"font-family: Courier New, Courier, monospace;\">\u00a0<\/span><\/div>\nOur data will be a simple array of object literals. Each object will have a name and value. Our chart will need to consume this data and render the bars accordingly. Since this is another simple literal value, we'll use <code>$R.state()<\/code> again, and default to an empty array.\n\n<!-- wp:code {\"language\":\"javascript\",\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>function BarChart(svg) {\n  this.width = $R.state(200);\n  this.height = $R.state(200);\n  this.data = $R.state([]);\n}<\/code><\/pre>\n<!-- \/wp:code -->\n<h2>Y Scale<\/h2>\n<div style=\"clear: both; text-align: center;\"><a style=\"margin-left: 1em; margin-right: 1em;\" href=\"http:\/\/1.bp.blogspot.com\/-aKQNtH00Wis\/UW8s53fUIiI\/AAAAAAAAAAg\/jzxAj-MTvoE\/s1600\/bar__0001_Y-Scale.png\"><img src=\"http:\/\/1.bp.blogspot.com\/-aKQNtH00Wis\/UW8s53fUIiI\/AAAAAAAAAAg\/jzxAj-MTvoE\/s1600\/bar__0001_Y-Scale.png\" alt=\"\" border=\"0\" \/><\/a><\/div>\nIn D3 we use scales to map the values in our data set to actual pixels on the screen. We do this by specifying a domain (the max and min of our data) and a range (the max and min dimensions of our chart in pixels). In this case our Y scale will need to have access to the chart's height, and our data, so that it can create a scale that maps our data to proper pixel heights on the screen.\n\nWe'll define our Y scale as a function, and then \"reactify it\" so that it can represent the Y scale value in our visualization. Since our Y scale needs access to our chart's height and data, we'll need to bind it to those values so that they can update our scale when they change.\n\n<!-- wp:code {\"language\":\"javascript\",\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>function BarChart(svg) {\n  this.width = $R.state(200);\n  this.height = $R.state(200);\n  this.data = $R.state([]);\n  var y = $R(BarChart.y).bindTo(this.height, this.data)\n}\nBarChart.y = function (height, data) {\n  return d3.scale.linear()\n    .domain([0, d3.max(data, function(d) { return d.value })])\n    .range([height, 0]);\n}<\/code><\/pre>\n<!-- \/wp:code -->\n<h2>X Scale<\/h2>\n<div style=\"clear: both; text-align: center;\"><a style=\"margin-left: 1em; margin-right: 1em;\" href=\"http:\/\/4.bp.blogspot.com\/-YXlyp0jqbgY\/UW8s9nirWHI\/AAAAAAAAAA4\/k7AT1_gub0E\/s1600\/bar__0002_X-Scale.png\"><img src=\"http:\/\/4.bp.blogspot.com\/-YXlyp0jqbgY\/UW8s9nirWHI\/AAAAAAAAAA4\/k7AT1_gub0E\/s1600\/bar__0002_X-Scale.png\" alt=\"\" border=\"0\" \/><\/a><\/div>\nOur X scale behaves similarly, we'll use an ordinal scale to map our discrete bars onto actual X positions and widths on the screen. Our scale will need our data, and our chart's width to determine how things should map. We'll also use the <code>rangeBands<\/code> feature of D3's ordinal scales to figure out how wide each bar should be.\n\n<!-- wp:code {\"language\":\"javascript\",\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>function BarChart(svg) {\n  this.width = $R.state(200);\n  this.height = $R.state(200);\n  this.data = $R.state([]);\n  var y = $R(BarChart.y, this).bindTo(this.height, this.data);\n  var x = $R(BarChart.x, this).bindTo(this.width, this.data);\n}\nBarChart.y = function (height, data) {\n  return d3.scale.linear()\n    .domain([0, d3.max(data, function(d) { return d.value })])\n    .range([height, 0]);\n}\nBarChart.x = function (width, data) {\n  return d3.scale.ordinal()\n    .domain(d3.range(0, data.length))\n    .rangeRoundBands([0, width], .1);\n}<\/code><\/pre>\n<!-- \/wp:code -->\n<h2>Bars<\/h2>\n<div style=\"clear: both; text-align: center;\"><a style=\"margin-left: 1em; margin-right: 1em;\" href=\"http:\/\/2.bp.blogspot.com\/-NDaB-JQE5-Y\/UW8s9hou78I\/AAAAAAAAAA0\/DYj_84c33vY\/s1600\/bar__0003_Bars.png\"><img src=\"http:\/\/2.bp.blogspot.com\/-NDaB-JQE5-Y\/UW8s9hou78I\/AAAAAAAAAA0\/DYj_84c33vY\/s1600\/bar__0003_Bars.png\" alt=\"\" border=\"0\" \/><\/a><\/div>\nNow we need a value that actually represents the SVG Rectangles that will make up our bars. If you've used D3 before, the code below should look pretty familiar \u2014 it follows the standard D3 process to add, update, and remove bars from our chart. Our bars need the SVG group that contains them, our data, the chart's height, and the x and y scales to map our data values to actual coordinates. We define the function, reactify it, and bind it to the relevant values.\n\n<!-- wp:code {\"language\":\"javascript\",\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>function BarChart(svg) {\n  var svg = d3.select(svg);\n  var barContainer = svg.append(\"g\");\n  \n  this.width = $R.state(200);\n  this.height = $R.state(200);\n  this.data = $R.state([]);\n  var y = $R(BarChart.y, this).bindTo(this.height, this.data);\n  var x = $R(BarChart.x, this).bindTo(this.width, this.data);\n  var bars = $R(BarChart.bars, this).bindTo(barContainer, this.data, this.height, x, y);\n}\nBarChart.y = function (height, data) {\n  return d3.scale.linear()\n    .domain([0, d3.max(data, function(d) { return d.value })])\n    .range([height, 0]);\n}\nBarChart.x = function (width, data) {\n  return d3.scale.ordinal()\n    .domain(d3.range(0, data.length))\n    .rangeRoundBands([0, width], .1);\n}\nBarChart.bars = function (barContainer, data, height, x, y) {\n  var bars = barContainer.selectAll(\".bar\").data(data);\n  bars.enter().append(\"rect\")\n    .attr(\"class\", \"bar\");\n  bars.attr(\"x\", function(d, i) { return x(i); })\n    .attr(\"width\", x.rangeBand())\n    .attr(\"y\", function(d) { return y(d.value); })\n    .attr(\"height\", function(d) { return height - y(d.value); });\n  bars.exit().remove()\n}<\/code><\/pre>\n<!-- \/wp:code -->\n<h2>Labels<\/h2>\n<div style=\"clear: both; text-align: center;\"><a style=\"margin-left: 1em; margin-right: 1em;\" href=\"http:\/\/3.bp.blogspot.com\/-iH3lIBx311k\/UW8s9rn_HrI\/AAAAAAAAAA8\/WM6nzsHNv0Q\/s1600\/bar__0004_Labels.png\"><img src=\"http:\/\/3.bp.blogspot.com\/-iH3lIBx311k\/UW8s9rn_HrI\/AAAAAAAAAA8\/WM6nzsHNv0Q\/s1600\/bar__0004_Labels.png\" alt=\"\" border=\"0\" \/><\/a><\/div>\nLast but not least, we need to represent our labels. These will be SVG Text elements, contained in their own labels group. Like our bars, they'll need their container, as well as our data, x, and y scales.\n\n<!-- wp:code {\"language\":\"javascript\",\"showLineNumbers\":true} -->\n<pre class=\"wp-block-code\"><code>function BarChart(svg) {\n  var svg = d3.select(svg);\n  var barContainer = svg.append(\"g\");\n  var labelContainer = svg.append(\"g\");\n  \n  this.width = $R.state(200);\n  this.height = $R.state(200);\n  this.data = $R.state([]);\n  var y = $R(BarChart.y, this)\n    .bindTo(this.height, this.data);\n  var x = $R(BarChart.x, this)\n    .bindTo(this.width, this.data);\n  var bars = $R(BarChart.bars, this)\n    .bindTo(barContainer, this.data, this.height, x, y);\n  var labels = $R(BarChart.labels, this)\n    .bindTo(labelContainer, this.data, x, y);\n}\nBarChart.y = function (height, data) {\n  return d3.scale.linear()\n    .domain([0, d3.max(data, function(d) { return d.value })])\n    .range([height, 0]);\n}\nBarChart.x = function (width, data) {\n  return d3.scale.ordinal()\n    .domain(d3.range(0, data.length))\n    .rangeRoundBands([0, width], .1);\n}\nBarChart.bars = function (barContainer, data, height, x, y) {\n  var bars = barContainer.selectAll(\".bar\").data(data);\n  bars.enter().append(\"rect\")\n    .attr(\"class\", \"bar\");\n  \n  bars.attr(\"x\", function(d, i) { return x(i); })\n    .attr(\"width\", x.rangeBand())\n    .attr(\"y\", function(d) { return y(d.value); })\n    .attr(\"height\", function(d) { return height - y(d.value); });\n  \n  bars.exit().remove()\n}\nBarChart.labels = function (labelContainer, data, x, y) {\n  var labels = labelContainer.selectAll(\".label\").data(data);\n  labels.enter().append(\"text\")\n    .attr(\"class\", \"label\");\n\n  labels.attr(\"x\", function(d, i) { return x(i) + x.rangeBand() \/ 2 - 8; })\n    .text(function (d,i) { return d.name; })\n    .attr(\"y\", function(d) { return y(d.value) + 10; })\n\n  labels.exit().remove()\n}<\/code><\/pre>\n<!-- \/wp:code -->\n<h2>All together now<\/h2>\nThis JSFiddle shows our example functioning in its entirety. The inputs allow you send values to <code>chart.width()<\/code>, <code>chart.height()<\/code> and <code>chart.data().<\/code> Those three reactive values that we exposed on our BarChart object become our API. When we assign new values to them, data flows through our graph of values, changing the chart's representation in the process.\n\n<iframe src=\"http:\/\/jsfiddle.net\/mattbaker\/MV4Q3\/12\/embedded\/result,html,js,css\/\" width=\"100%\" height=\"400\" frameborder=\"0\" allowfullscreen=\"allowfullscreen\"><\/iframe>\n\nBecause we expressed how data flows through the various components that make up our visualization, there's no need for a master \"render\" method, we allow the chart to update itself. Note that only the parts of our chart affected by a given change will be updated. In our example that's everything, but in large visualizations the targeted updates Reactive.js provides let us avoid unnecessary re-rendering of untouched elements. The best part is, we get that behavior for free.\n\nHopefully this provides a small, tangible example of how thinking reactively can inform the design of your code. Stay tuned in the weeks to come as we address topics like reactive UIs, AJAX requests, and other fun applications!","excerpt":"New to Reactive.js? Check out last week&#8217;s introduction. All visualizations are ultimately a composition of smaller elements; data sets, scales, individual lines, and labels to name a few. Those elements relate to each other in different ways, and to manage their interdependency we typically find ourselves writing a master &#8220;render&#8221; method \u2014 something that can... <a class=\"moretag\" href=\"https:\/\/eng.wealthfront.com\/2013\/04\/18\/reactive-charts-with-d3-and-reactivejs\/\" aria-label=\"Read more about Reactive Charts with D3 and Reactive.js\">Read more<\/a>","date":"2013-04-18 10:05:00","author":"Wealthfront Engineering","categories":["d3","data visualization","javascript","reactive programming"],"tags":[]}]