Closed chancancode closed 8 years ago
if/unless tests
: PR: https://github.com/emberjs/ember.js/pull/13159I'll look at the "willDestroyElement" tests.
The key thing is it is supposed to be the inverse of didInsertElement so main thing is it runs before DOM teardown, so unlikely that this is covered by willDestroy which is async after DOM teardown. It also is supposed to only run if the didInsertElement hook ran.
@GavinJoyce There is a current bug in htmlbars with this lifecycle hook firing too late in the component helper. https://github.com/emberjs/ember.js/issues/13028
It also is buggy with the current each/else https://github.com/emberjs/ember.js/issues/12716
It also regressed parentView being available at 1.13 but that is private API and has been like that now for a while, not sure though if it is a reason for people being stuck.
Are there other tests covering lifecycle in glimmer? Probably should add them to any test that add/removes components. /cc @wycats @chancancode
Confirmed that the unported #with
test can be deleted.
#with
tests #13130log
testsdebug
testsI can take unbound
:lock:
I'll port the each-in
tests.
@chancancode - I think we can check off / remove the debug tests
item as well.
custom-helper-tests
.https://github.com/emberjs/ember.js/issues/13139 removes the unused glimmer-component
tests folder
I'm taking "Basic content rendering tests" (and fixing the implementation in Glimmer)
I'm taking "select
test :scissors:"
Updating to match style introduced in 5c12157
collection
test :scissors: " https://github.com/emberjs/ember.js/pull/13161I'm taking a look at input
element tests if they're still not locked.
I'll take a look on
I'm not familiar with Glimmer2 yet. Anyway #13103 is merged now so I'll try to figure out how to implement it.
I need to work on a bug on closure components, so I'll take closure component
's test
We are implementing lifecycle hooks, :lock:-ing the tests :ok_hand:
"void element" test #13187 :scissors:
block params
test #13189
:wave: I'll take:
{{input}} tests
: PR: https://github.com/emberjs/ember.js/pull/13194I'll take the yield tests
I'll also go ahead and take the attrs_lookup
tests : PR #13203
I've opened #13199 for the partial
helper tests.
Taking the binding integration
tests too
{{yield}}
testsOpen #13214 for closure component
tests.
{{tesxtarea}}
testsI'll take the view
helper tests and all the things that it touched.
Just wanted to jump in and thank everyone for helping us out! 😄 My apologies for the delay – we are slowly digging ourselves out of the backlog; we are closer than it appears on Github's progress bar, because a lot of the :lock:ed items already have PRs waiting for review 🎉
I'll take the {{#each}}
test: #13349
I'll take the "local lookup" test
it looks like the system/lookup-helper_test.js
file is testing the actual findHelper
method, which appears to me to be covered by integration/helpers/custom-helper-tests.js
. Doesn't seem to me that we're unit testing the actual ember-glimmer
lib, so maybe ✂️ ? @chadhietala @asakusuma since both of you touched helper-lookup-related tests can you confirm?
@Joelkang I can't recall anything related to your question, what exact files have I touched that are related? If I can look at the git commit where I touched it, might jog my memory.
@asakusuma oh i just meant that since you're working on the local lookup test, to see if there's any commonality there at all
integration/helpers/custom-helper-tests.js
doesn't appear to be testing local lookup. Also, local lookup is not working in glimmer right now, which I'm working on fixing.
render env tests are snipped. Looking at "bootstrap" tests now, many of which do need to be ported with the functionality (using <script type="text/x-handlebars" data-template-name="foo">
).
Did a simple migration of mutable bindings
here: https://github.com/emberjs/ember.js/pull/13456
Closure components tests were already merged a couple of weeks ago.
Thank you all for the hard work here! Closing this in favor of an updated listing/issue: #13644.
:recycle: :recycle: :recycle: Please consider the environment before printing this Github issue. :recycle: :recycle: :recycle:
The Backstory
@wycats and I (and many other people who have helped along the way) have been working on a rebuild of the rendering engine for the last six months, codenamed "Glimmer 2".
It started as a fork of htmlbars but almost every line of code has been rewritten (sometimes several times) by now. We distilled all the learnings from building the previous-generation rendering pipelines (handlebars, htmlbars, the original glimmer project, etc), resulting in an architecture that's simultaneously more fitting for Ember's use cases but is also more flexible and well-positioned for future extensions and other non-Ember use cases.
You can find the codez at https://github.com/tildeio/glimmer. It's (re)written in TypeScript, and I think it's pretty cool. Anyway, more on that at EmberConf.
The Integration
While there are still a lot of work (mostly optimizations) we would like to do on the engine, we believe we have implemented enough to cover all the basic Ember use cases. So about two months ago, we have started integrating the new engine into Ember proper. You can follow this meta-issue for our progress so-far.
While it is not yet possible to use the new engine in real apps just yet, we expect that work to be completed relatively soon. The expectation is that once we have finished the implementation, it will be a relatively painless, drop-in upgrade for apps, just like the original Handlebars to HTMLBars migration.
(It should be noted that the ability to toggle the feature flag will probably land before all the existing features are implemented, so it will probably not work seamless with your app from the get-go.)
Pls send halp
So, you might be wondering, "How can I help?"
I am glad you asked! :boom: :sparkles: :fireworks: :tada:
At this point, the highest value you can do to help is to help port the existing tests (and help reviewing these PRs). You see, Ember has a pretty extensive test suite that tests the "view layer" behavior. The problem is that a lot of these tests are written in ways that are quite coupled to the existing implementation or otherwise uses legacy semantics (such as
{{view.foo}}
) that are no longer supported.In order to be certain that we did not cause any regressions, we would like to be able to run our entire test suite against both the current rendering engine ("htmlbars") and Glimmer 2.
We have written a new test harness that allow us to do just that. I will get into the technical details below, but the basic idea is that we have written our test cases against an abstraction layer that encapsulates the differences between the two engines, allowing the same code in the test cases to run against both implementations.
Along the way, we are also "modernizing" the existing tests in the process to not rely on legacy semantics (unless they appear to be explicitly testing those semantics, in which case we will leave them alone). Our new test harness also makes it much easier and much more pleasant to do "matrix style" tests. More on that below, but here is a high-level architecture diagram:
The net result is that the tests are now much easier to read and reason about, and we have also greatly increased coverage. This is great outcome for everybody, but we still have a lot more tests left, and we cannot feel confident shipping the engine without all of them being ported. However, if a few of you could help us port a test file each, we will be in very good shape by this time next week!
How the harness works
The actual mechanism we used is pretty low tech. You might have heard of it, it's called symbolic links.
Inside the test folder of
ember-glimmer
package, you will find a file calledabstract-test-case.js
, which is also symlinked to the same location inside theember-htmlbars
package. This file defines the APIs we use the write the test cases. Because this file is shared (symlinked) between both packages, it does not contain anything specific about the two implementations.Instead, all the differences are abstracted by importing files (such as
import ... from './helpers'
) provided by each package. Alternatively, each package can also override specific methods on the "abstract" classes intest-case.js
(see theember-glimmer
vsember-htmlbars
versions).(In a lot of cases, you might not even need to modify these files at all, but knowing that's how it works/where the work happen under-the-hood might be still be helpful if you run into issues.)
How the tests work
Since the engine is intended to be a drop-in upgrade for real apps, as long as we the tests are actually testing how these features are supposed to be used in the real world, there are no reason why the tests wouldn't run with both engines.
That's been our focus so far. You can see an example here.
This test is physically located inside the
ember-glimmer
directory, but it is symlinked to the same location in theember-htmlbars
directory (in fact, the entire directory is symlinked).As you can see, the test imports this package-specific
test-case.js
, but otherwise is agnostic about the rendering engine implementation.The process
In general, and at the high-level, the process looks like this:
ember-htmlbars
)ember-glimmer/tests/integration/...
somewheremoduleFor
and ES6 class formatember-htmlbars
package, unless the parent folder is already a symlink inember-htmlbars
(like the concat test I showed above)How to write good tests
Here are a few general tips/rules that you can follow to improve the test cases.
The "I-N-U-R" cycle
We would like each test to go through the "I-N-U-R" cycle:
Initial render
Render the template you want to test, with the initial values of your choice (
this.render(..., { ... })
) and assert the result is what you expect. (Example)No-op re-render
Call
this.runTask(() => this.rerender());
without any changes to the values, then assert that the result remain the same. (Example)Update(s) via mutation(s)
Make some updates to the values you use in the templates. (Example)
You should try to:
this.runTask
+ assertion) if it makes sense. This increase chances of catching "clobbering" bugs where updating some of the values will "blow away" another unrelated part of the template or causes other undesirable effects.pushObject
vs removing an item, etc), it's usually a good idea to try more than one of them. (Example)Reset via replacement
Reset to the original starting condition by replacing all the variables.
Avoid duplicating tests
It is easy to copy a test case a few times to test slightly different variations of the same thing (e.g.
{{#if foo}}
starting with true vs false, or the difference between a "POJO" vs anEmber.Object
), and we have done that a lot in the existing tests.Sometimes that's the best you can do, but there are a lot of problems with this. First it produces a large amount of tests physically in the file, making it hard to find things. Also, when someone need to add a new test, they will usually randomly pick one of the few variants, carrying over details/mistakes that doesn't make a lot of sense for the new scenario. When we fix bugs in one of the copies, we will probably forget the rest.
Usually, there are ways to avoid the duplication. For example, in the case of testing the different starting condition (
{{#if foo}}
againsttrue
andfalse
), you can just test both starting conditions in the same test:In other cases, we have been able to define shared behaviors by extracting some shared super classes (putting the actual test cases in the super class), and configure the parts that are different in the subclass. This allows you to write the test cases once, and automatically have them run many different scenarios (the "Matrix Style" tests).
The best example is probably the conditionals test (
if
,unless
, etc). The actual test file simply defines the template invocation style and subclassTogglingSyntaxConditionalsTest
(located inshared-conditional-tests.js
) and automatically get many shared tests.The "inline if/unless" tests takes this even further, running the same set of test cases against 11 (!) different invocation scenarios.
The actual structure of the sharing was somewhat difficult to arrive at, and took some time to mature/get right, but the payoff there was massive (the basic scenarios are now shared between
{{#with}}
and{{#each}}
as well).Legacy semantics
A lot of the existing tests uses legacy semantics like views,
{{view.foo}}
,{{#view}}
,context
, controllers, etc. Most of the times, this is purely incidental, and just a result of the test being written in a time where those primitives were the main way to do things. In those cases, you can usually port them to the new harness (which uses components instead) without problems.When in doubt, you can also a test unported in the first iteration of your PR and ask your questions in a line comment.
To
attrs
or not toattrs
We decided to default to not using
{{attrs.foo}}
in tests that uses curly components (which is almost all of them) and rather rely just relying on the attrs being reflected onto the property with the same name (i.e. just{{foo}}
). Unless the test is specifically about testingattrs.*
(those should probably all be in the same file), you should generally prefer{{foo}}
rather than{{attrs.foo}}
for consistency. You can always name things likeinnerFoo
vsouterFoo
if you feel the need to disambiguate.See also https://locks.svbtle.com/to-attrs-or-not-to-attrs by @locks.
Caveats
Sometimes you the tests that you ported might not work on either Glimmer 2 or HTMLBars. (If it doesn't work on either engine, you probably did something wrong.)
In the case where it doesn't work on Glimmer 2, it's probably because we haven't implemented that feature completely yet. (For example, we have implemented basic components support but haven't implemented
attributeBindings
at this point.)In this case, it is still good to port the test to the new style, so that we can simply enable it when the feature is implemented. To disable a test temporarily for Glimmer 2, you can simply replace the
@test
prefix in the method name to@htmlbars
(which means "run this in HTMLBars-only"). You can also disable an entire module by prefixing itsmoduleFor
name with@htmlbars
.In some rare cases, the test will work correctly on Glimmer 2 but does not pass on HTMLBars. You should double check that the semantics you are testing is correct, but it is also quite possible that there is simply a bug in the current implementation of HTMLBars. (This usually happens when we test some HTMLBars features with the new "matrix style", where the values do not update correctly in some edge cases.)
In that case, you can similarly flag an individual test case or an entire module as
@glimmer
. Since this is expected to be quite rare (and might require a bug fix), it would be helpful if you can include a brief description of the problems you encounter in a line comment.Examples
Here are some of the great examples where our community members have helped port existing tests:
12920 Inline
{{if}}
helper12927
{{#with}}
13019 Inline
{{unless}}
13093
(hash)
helperAs you can see, the earlier iterations was more difficult (we were still figuring out the shared test cases story), but the latter attempts were relatively straight forward. Thank you @GavinJoyce and @chadhietala for paving the way!
So... where do I start?
Here is a list of good starting points. If you are serious about working on one of these, you probably want to leave a comment below so that other people will know not to work on that. (If you ran out of time or encountered great difficult, please come back to "release the lock" and/or push your WIP work, so other people can pick it up!)
[x] Basic content rendering tests
#13141by @chancancodeI don't know if this already exists. Please try to find it and port if it does. But otherwise, please create a new file for it (we already started something in https://github.com/emberjs/ember.js/blob/master/packages/ember-glimmer/tests/integration/content-test.js). The idea is we want to test "what happens if you put a strange thing into the DOM", e.g.
{{foo}}
, wherefoo
isundefined
,null
, and object, etc. This is a prime target for the "Matrix Style" test, so you might want to study up on how the test harness works and draw ideas from the conditionals tests.This should also absorb https://github.com/emberjs/ember.js/blob/master/packages/ember-htmlbars/tests/hooks/text_node_test.js too.
[x]
attr_nodes
tests (:lock: by @chancancode and @wycats)We would like to take a closer look at these tests and understand the requirements. Locking it for now.
[ ]
compat
tests :scissors:We announced we will end support for the legacy addons by 2.6, so we won't need to support these features in Glimmer 2. Please open PRs to remove the tests and the features on master. (This would probably require relatively deep knowledge of the codebase.)
[x]
glimmer-component
tests :scissors:#13139by @lorcanThis folder is unused. Please send a PR to remove it.
Helpers (I think we should move them into
tests/integration/helpers
)-html-safe
:lock:I'm not sure if this is needed for Glimmer 2. Locking for now.
closure component
(:lock: by @Serabe)I am pretty sure this will have to be
@htmlbars
for now.collection
test :scissors:#13161by @HeroicEricThis is legacy. Please open a PR to remove the test and any code you could find that implements the feature.
#component
helper#13140by @GavinJoyceBasic support for the feature has landed in #13057 and we already wrote some basic tests. Please port the rest of the tests into the new file (keep an eye for things that are already tested in the new file). I expect most of them to pass except position params which is not yet implemented in Glimmer 2 (you can make them
@htmlbars
for now).#13138by @zackthehumanBasic support for the feature has landed in #12910/#13087 and we already wrote some basic tests. Please port the rest of the tests into the new file (keep an eye for things that are already tested in the new file). I expect most of them to pass with the exception of the lifecycle stuff (destroy, etc) for class-based helpers (you can make them
@htmlbars
for now).debug
tests :scissors:#13129by @code0100funThis is a duplicate of
log
as far as I can tell. See notes onlog
tests below.#each-in
tests#13136by @mmunThis should be ported to
test/intergration/syntax/...
. (In general, what was previously known as "block helpers" are now implemented as "syntax" in Glimmer 2.)This helper is not implemented in Glimmer 2 and will be pretty difficult for a new contributor to implement it. However, it is still important to port the tests asap (and make the module
@htmlbars
).For bonus points, you might want to think about how to share the basic tests with the rest of the conditional matrix (i.e. testing when we need to go into the "default" branch and when we need to go into the "inverse"/
{{else}}
branch). See #13048 for some inspiration.#each
tests 🔒 by @JoelkangThis should be ported to
test/intergration/syntax/...
. (In general, what was previously known as "block helpers" are now implemented as "syntax" in Glimmer 2.)Basic support for the feature has landed in #13048 and we already wrote some basic tests. Please port the rest of the tests into the new file (keep an eye for things that are already tested in the new file). I expect most of them to pass with the exception of the lifecycle stuff (destroy, etc) for class-based helpers (you can make them
@htmlbars
for now).For bonus points, you might want to think about how to share the basic tests with the rest of the conditional matrix (i.e. testing when we need to go into the "default" branch and when we need to go into the "inverse"/
{{else}}
branch). I think we already did that part in #13048, but if you see other ways to improve it or do more sharing please feel free to suggest them.get
tests#13173,#13264by @ro0grThis helper is not implemented in Glimmer 2 and will be pretty difficult for a new contributor to implement it. However, it is still important to port the tests asap (and make the module
@htmlbars
).(Actually, it's not that hard – the implementation will likely not take more than 10-20 lines, but you would need to be quite familiar with how Glimmer 2 works to do it. Once #13103 is completed it might give you some ideas.)
I believe this is already ported by @GavinJoyce. The rest are probably just legacy stuff that we can remove. @GavinJoyce can you confirm?
{{input}}
tests (:lock: by @GavinJoyce)This helper is a not implemented in Glimmer 2 and will be pretty difficult for a new contributor to implement it. However, it is still important to port the tests asap (and make the module
@htmlbars
).(More precisely, the helper uses component features that are not yet implemented, such as attribute bindings. Once they are implemented the tests will probably Just Pass™.)
loc
tests#13129by @code0100funThe helper is not implemented in Glimmer 2, but should be trivial if you want to do it. (See #12910 or #13093) Otherwise you can always mark the module as
@htmlbars
.log
tests#13131by @green-arrowThe helper is not implemented in Glimmer 2, but should be trivial if you want to do it. (See #12910 or #13093) Otherwise you can always mark the module as
@htmlbars
. As mentioned above, I thinkdebug_test.js
is just a duplicate of this, please verify and delete that file. As an exception, we only want to test initial render here, not the usual "I-N-U-R" cycle.partial
tests#13199,#13306by @jheth and @chadhietalaThis functionality is not implemented in Glimmer 2 and will be pretty difficult for a new contributor to implement it. However, it is still important to port the tests asap (and make the module
@htmlbars
). Please consider adding some abstractions likethis.registerPartial
.text_area
tests#13215by @kiwiupoverThis helper is a not implemented in Glimmer 2 and will be pretty difficult for a new contributor to implement it. However, it is still important to port the tests asap (and make the module
@htmlbars
).(More precisely, the helper uses component features that are not yet implemented, such as attribute bindings. Once they are implemented the tests will probably Just Pass™.)
unbound
tests#13137by @chadhietalaThis helper is not implemented in Glimmer 2 and will be pretty difficult for a new contributor to implement it. However, it is still important to port the tests asap (and make the module
@htmlbars
).(Actually, it's not that hard – the implementation will likely not take more than 10-20 lines, but you would need to be quite familiar with how Glimmer 2 works to do it. Once #13103 is completed it might give you some ideas.)
view
tests (:lock: by @chadhietala)We announced we will end support for the legacy addons by 2.6, so we won't need to support these features in Glimmer 2. Please carefully review these tests and see if there are anything that doesn't look like deprecated/legacy functionality. Otherwise, please open PRs to remove the tests and the features on master. (This would probably require relatively deep knowledge of the codebase.)
with
testsI believe this is already ported by @chadhietala. The rest are probably just legacy stuff that we can remove. @chadhietala can you confirm?
yield
tests (:lock: by @kiwiupover)The feature should work in Glimmer 2 (as @chadhietala pointed out in https://github.com/emberjs/ember.js/pull/13093#discussion_r55926094). Please port the rest of the tests into a new file. I expect most of them to pass. There are a lot of legacy stuff in there, so please try to understand the spirit of the test and see if they are still needed (vs they are testing a legitimate thing but just happen to use legacy semantics to test them, in which case, you should port them using non-legacy semantics).
"Integration" tests
The actual
attributeBindings
feature on components is not yet implemented, but this file doesn't seem to be testing that at all. In fact, I cannot tell what this file is testing at all. Please do investigate! (I suspect this is something we already tested, perhaps @GavinJoyce or @chadhietala will know.)#13203by @JoelkangThis is probably the one place where it makes sense to test
{{attrs.foo}}
vs{{foo}}
. I expect them to already work in Glimmer 2. However, components lifecycle hooks (e.g.didReceiveAttrs
) is not yet implemented, so you would have to either port the test without using them, or tests that needs them as@htmlbars
for now.#13210by @JoelkangSome of these tests belongs in other files (e.g. helper without parameters should be tested inside helper tests, undefined property probably belongs in the "Basic content rendering tests". The
Binding
and computed property tests are fine here, and they should Just Work™ on Glimmer to with some modernizing. (We might want to be want to be a little bit more through with CPs if this turns out to be the only place that tests them – like updating a dependent key updates the output, etc.) The view stuff seems largely incidental, you should be able to rewrite them without the legacy semantics, but there does seem to be one or two tests that are just testing legacy semantics (based on a quick scan). Please do investigate!#13189by @JoelkangI think we should be able to find a better home the stuff tested in here (like in the helpers and components files), but even just straight porting them would be helpful.
elementId
tests#13208by @jhethThis should be tested in https://github.com/emberjs/ember.js/blob/master/packages/ember-glimmer/tests/integration/components/curly-components-test.js and I think it should just work in Glimmer 2. It probably doesn't make sense to test updating for this test – I don't think we support updating the
elementId
, but please do investigate!(If we start adding more tests for components, it probably makes sense to start splitting them up into different modules/files.)
#12890by @SerabeThis is the monster file that tests all things components. It should probably be tested in https://github.com/emberjs/ember.js/blob/master/packages/ember-glimmer/tests/integration/components/curly-components-test.js, but as I said above, we probably want to start breaking things up. Some of the features are not implemented Glimmer 2 yet, so feel free to use
@htmlbars
liberally.Most of these functionality are not yet implemented in Glimmer 2, so you might have to
@htmlbars
the entire module.#13143by @code0100fun +#13259I think we should be able to find a better home the stuff tested in here (like in the content tests file), but even just straight porting them would be helpful.
#13147by @chadhietalaI think this must already be tested in the helpers test? Please do investigate and open a PR to remove if true.
This is testing the
<input>
HTML element, not the{{input}}
helper. I won't be surprised if a lot of them doesn't work in Glimmer 2 yet, but it would be very helpful to know. Please port the test cases and flag with@htmlbars
as needed.I'm not sure if this is implemented in Glimmer 2 yet. Please port the test cases and flag with
@htmlbars
as needed.The Glimmer 2 implementation might change the story a bit, locking for now.
select
test :scissors:#13144by @HeroicEricThis is legacy. Please open a PR to remove the test and any code you could find that implements the feature.
#13146by @chadhietalaI'm pretty sure this is already tested somewhere in the
if/each
tests. (The concept "tagless views" doesn't make any sense because even in htmlbars they are not implemented as views anymore.) If I am wrong, please port them into theif/each
test files as appropriate and :scissors: this.#13187by @MatrixZI'm pretty sure this is already tested in the components test. (
tagName
is not implemented yet, but it doesn't seem important for the test.) If I am wrong, please port them into the curly component test files as appropriate and :scissors: this.I don't think the
willDestroyElement
hook is implemented in Glimmer 2, but thewillDestroy
hook is (and we already have tests for them in https://github.com/emberjs/ember.js/blob/master/packages/ember-glimmer/tests/integration/components/curly-components-test.js#L202).Please investigate if there are any semantic differences (ordering, etc) between the two hooks. If they have the same semantics, we might just want to merge the two tests and test it "matrix style" (please check if the two tests are actually testing the same thing, if not, it's perfectly fine to have > 1 test).Otherwise please port it with@htmlbars
.#13149by @chadhietalaThe
{{view}}
part is obviously legacy, if there are something that we didn't otherwise cover in the{{#with}}
tests, please port them there, otherwise :scissors: /cc @chadhietala[ ] "View node manager" test :scissors: :question:
The implementation of the
ViewNodManager
probably need to stick around for a little longer because some internal things are still implemented as views in htmlbars, but this doesn't seem like it's testing anything important, so we can probably :scissors: it? @rwjblue can you confirm?"System" tests
#13148by @chadhietalaThis is likely legacy. Please do investigate!
This seems to be testing
Ember.TEMPLATES
. I don't know if this is legacy, locking for now. @rwjblue can you confirm and update this item? If it's legacy, we can just :scissors: this and the implementation. If it's not, I assume it's already handled at the container/resolver level and they should Just Work™ in Glimmer 2 after porting.Please do investigate what this is testing, and see if it could be merged into the helpers integration tests.
This seems to be testing 'view.env`. I don't know if this is legacy, locking for now. @rwjblue/@wycats can you confirm and update this item?
There are probably other tests that needs to be ported too. If you noticed anything I missed, please mention them in the comments.
Reviews
Once you are ready to submit your PR (please feel free to submit WIP PRs!), please reference this issue in your PR description, so that we can review them.
Timeframe
We want to get as many test ported as soon as possible. Ideally, we would like to have most (if not all) of the tests ported within the next week or two.
Thank you in advance for your help! :heart: :yellow_heart: :green_heart: :blue_heart: :purple_heart: