Slide 1

Slide 1 text

Unit Testing JavaScript When You’re Afraid Of JavaScript

Slide 2

Slide 2 text

Confession Before we start

Slide 3

Slide 3 text

I am SCARED of JavaScript

Slide 4

Slide 4 text

Sometimes it’s the language. Anyone want to guess what this does?

Slide 5

Slide 5 text

> Math.max() Sometimes it’s the language. Anyone want to guess what this does?

Slide 6

Slide 6 text

> Math.max() -Infinity Sometimes it’s the language. Anyone want to guess what this does?

Slide 7

Slide 7 text

> Math.max() -Infinity > Math.min() Sometimes it’s the language. Anyone want to guess what this does?

Slide 8

Slide 8 text

> Math.max() -Infinity > Math.min() Infinity Sometimes it’s the language. Anyone want to guess what this does?

Slide 9

Slide 9 text

> Math.max() -Infinity > Math.min() Infinity > Math.max() > Math.min() Sometimes it’s the language. Anyone want to guess what this does?

Slide 10

Slide 10 text

> Math.max() -Infinity > Math.min() Infinity > Math.max() > Math.min() false Sometimes it’s the language. Anyone want to guess what this does?

Slide 11

Slide 11 text

How about object construction?

Slide 12

Slide 12 text

> "I am a string" instanceof String How about object construction?

Slide 13

Slide 13 text

> "I am a string" instanceof String false How about object construction?

Slide 14

Slide 14 text

> "I am a string" instanceof String false > String("abc") instanceof String How about object construction?

Slide 15

Slide 15 text

> "I am a string" instanceof String false > String("abc") instanceof String false How about object construction?

Slide 16

Slide 16 text

> "I am a string" instanceof String false > String("abc") instanceof String false > (new String("abc")) instanceof String How about object construction?

Slide 17

Slide 17 text

> "I am a string" instanceof String false > String("abc") instanceof String false > (new String("abc")) instanceof String true How about object construction?

Slide 18

Slide 18 text

> "I am a string" instanceof String false > String("abc") instanceof String false > (new String("abc")) instanceof String true > String("abc") == (new String("abc")) How about object construction?

Slide 19

Slide 19 text

> "I am a string" instanceof String false > String("abc") instanceof String false > (new String("abc")) instanceof String true > String("abc") == (new String("abc")) true How about object construction?

Slide 20

Slide 20 text

Every number is a floating point, which means 1.0 + 1.0 does not equal what you think it does.

Slide 21

Slide 21 text

How about crazy APIs? You’d think that new-ing an Array with 4 would initialize with that value. Nope. Creates 4 empty values. Oh, and type coercion is pretty awesome too.

Slide 22

Slide 22 text

> ",,," == new Array(4) How about crazy APIs? You’d think that new-ing an Array with 4 would initialize with that value. Nope. Creates 4 empty values. Oh, and type coercion is pretty awesome too.

Slide 23

Slide 23 text

> ",,," == new Array(4) true How about crazy APIs? You’d think that new-ing an Array with 4 would initialize with that value. Nope. Creates 4 empty values. Oh, and type coercion is pretty awesome too.

Slide 24

Slide 24 text

Bad advice everywhere.

Slide 25

Slide 25 text

Bad advice everywhere.

Slide 26

Slide 26 text

The browser is the most hostile software engineering environment imaginable. - Douglas Crockford So let’s take this nitroglycerin and throw it into the Fire Swamp. We’ll never survive. Nonsense, you’re only saying that because no one ever has.

Slide 27

Slide 27 text

CAN I VERTICALLY CENTER MY CONTENT PLEASE? W3C changed the spec and there are 3 revisions. No polyfill for the latest. This stuff’s crazy

Slide 28

Slide 28 text

Even when something gets implemented everywhere, you run into problems. LocalStorage sounds great Alternative? IndexedDB - no mobile, IE < 10

Slide 29

Slide 29 text

Sometimes you just want to give up CSS - ever happened to you? Gives you 47 minutes. CSS Animations -> jQuery, etc. It’s amazing we can build anything at all.

Slide 30

Slide 30 text

Everyone is self-taught More of a cultural issue

Slide 31

Slide 31 text

Our definitive guidebook is a primer on which parts of the language *not* to use. Does this seem weird?

Slide 32

Slide 32 text

Battle Plan Give up? Hell no. But to get out alive, we need to have a strategy My secret weapon:

Slide 33

Slide 33 text

Testing Fail as quickly as possible. No static types or compiler Test all the time. Test everything. Treat tests as your compiler. Test as your linter. Live and die by it.

Slide 34

Slide 34 text

Matt Steele @mattdsteele http://matthew-steele.com

Slide 35

Slide 35 text

Tools Code Tips The tools I use for testing how it changes the code you write some tips on how to get started

Slide 36

Slide 36 text

Tools Code Tips The tools you use, how it changes the code you write, and some tips on how to get started

Slide 37

Slide 37 text

You might have done testing with Selenium or other point-n-click tools. I call these “integration” or “system” tests.

Slide 38

Slide 38 text

Problems Google ended up writing thousands for GMail. Turns out, it’s unsustainable. Took hours for test suites to run Can’t rely on them as a safety net

Slide 39

Slide 39 text

Problems Google ended up writing thousands for GMail. Turns out, it’s unsustainable. Took hours for test suites to run Can’t rely on them as a safety net

Slide 40

Slide 40 text

•Slow Problems Google ended up writing thousands for GMail. Turns out, it’s unsustainable. Took hours for test suites to run Can’t rely on them as a safety net

Slide 41

Slide 41 text

•Slow •Depends on a browser Problems Google ended up writing thousands for GMail. Turns out, it’s unsustainable. Took hours for test suites to run Can’t rely on them as a safety net

Slide 42

Slide 42 text

•Slow •Depends on a browser •Brittle Problems Google ended up writing thousands for GMail. Turns out, it’s unsustainable. Took hours for test suites to run Can’t rely on them as a safety net

Slide 43

Slide 43 text

•Slow •Depends on a browser •Brittle •Coupled to implementation Problems Google ended up writing thousands for GMail. Turns out, it’s unsustainable. Took hours for test suites to run Can’t rely on them as a safety net

Slide 44

Slide 44 text

•Slow •Depends on a browser •Brittle •Coupled to implementation •Debugging woes Problems Google ended up writing thousands for GMail. Turns out, it’s unsustainable. Took hours for test suites to run Can’t rely on them as a safety net

Slide 45

Slide 45 text

Unit Testing Spend the rest of the time discussing Unit Testing. Don’t test your system. Instead, test individual units of functionality Write lots of unit tests, each testing one thing

Slide 46

Slide 46 text

describe("A test suite", function() { var a; it("can perform assertions", function() { a = true; expect(a).toBe(true); }); }); Lots out there. I like Jasmine. Similar to RSpec. Reads like a sentence: - I expect a to be true

Slide 47

Slide 47 text

it("does regular expressions", function() { var message = 'foo bar baz'; expect(message).toMatch(/bar/); expect(message).toMatch('bar'); expect(message).not.toMatch(/quux/); }); Has tons of assertions built in, including regular expressions

Slide 48

Slide 48 text

var makeExcited = function(str) { if (str) { return str.toUpperCase() + '!!1'; } } Here’s how you’d use it. I’ve written a function to make a string excited.

Slide 49

Slide 49 text

describe('Make Excited', function() { it('uppercases and adds exclamations', function() { var excited = makeExcited('nebraska code camp'); expect(excited).toBe('NEBRASKA CODE CAMP!!1'); }; it('handles no value gracefully', function() { expect(makeExcited()).toBeUndefined(); }; }); Here’s what its test would look like. You can test “happy path” But the key is testing edge cases. What happens if you pass an object?

Slide 50

Slide 50 text

As you build your tests into suites, you can run them. Really fast - took 18 milliseconds to run all your tests At this speed, you can have your tests running every time you save

Slide 51

Slide 51 text

When tests fail, you get descriptive info.

Slide 52

Slide 52 text

Open-source, so people have built on top of this. jasmine-jquery: worth checking out Lots of assertions to make your tests more descriptive

Slide 53

Slide 53 text

Some of these are nuts. ImageDiff lets you test out Canvas code by passing in two images It compares the images, and whatever’s different shows up like this monocle.

Slide 54

Slide 54 text

Another nice tool is Sinon. Fake out Ajax calls Timers, so you can test your setTimeout methods directly More flushed mock objects

Slide 55

Slide 55 text

Tools Code Tips How do you integrate this into your workflow? Not as simple as you’d think

Slide 56

Slide 56 text

Example Time Let’s take these tools and apply them to the simplest application ever.

Slide 57

Slide 57 text

No content

Slide 58

Slide 58 text

jQuery(function($) { }); How to make the form submit code work. Using jQuery To start, we’ll be using jQuery and binding to the Document.Ready

Slide 59

Slide 59 text

jQuery(function($) { $('form').on('submit', function() { }); }); We want to bind to when the form submits, something happens

Slide 60

Slide 60 text

jQuery(function($) { $('form').on('submit', function() { $.ajax({ url: '/statuses', type: 'POST', dataType: 'json', }); return false; }); }); We return false, and make an ajax request. We specify the endpoint, method, and what gets returned.

Slide 61

Slide 61 text

jQuery(function($) { $('form').on('submit', function() { $.ajax({ url: '/statuses', type: 'POST', dataType: 'json', data: {text: $(this).find('textarea').val()}, }); return false; }); }); We also want to pass data, so we’ll do a query to find the text inside the textarea

Slide 62

Slide 62 text

jQuery(function($) { $('form').on('submit', function() { $.ajax({ url: '/statuses', type: 'POST', dataType: 'json', data: {text: $(this).find('textarea').val()}, success: function(data) { $('#statuses') .append('
  • ' + data.text + '
  • '); } }); return false; }); }); And when the ajax call returns, we expect it’ll contain a “text” JSON field. We then find our #statuses field and create a new list item containing what got returned

    Slide 63

    Slide 63 text

    Now try to test that. Okay, so let’s write a unit test for that

    Slide 64

    Slide 64 text

    (ʘ‿ʘ) No problem, I’ll just start with... wait, document.ready. How do I repeat this every time?

    Slide 65

    Slide 65 text

    (ಠ_ಠ) Also need Ajax Same endpoint? Guess I can fake, but now have two things

    Slide 66

    Slide 66 text

    (ಥ_ಥ) A couple test cases (empty data, fast submission, errors), each requiring duplicate setup Also, need to pull in the HTML or duplicate it verbatim? Ugh

    Slide 67

    Slide 67 text

    (! ಠӹಠ) ! WHY IS THIS SO HARD?! SERIOUSLY I JUST WANT TO TEST A FORM SUBMISSION

    Slide 68

    Slide 68 text

    (ϊಠӹಠ)ϊኯᵲᴸᵲ 87% of attempts to unit test your JavaScript ends in a table flip. True story.

    Slide 69

    Slide 69 text

    Doesn’t Happen Sad truth is, this is way too common. And means we don’t test our code.

    Slide 70

    Slide 70 text

    Lincoln GitHub - find users with location Pull down repos Check for ‘test’ or ‘spec’ files Probably overestimating.

    Slide 71

    Slide 71 text

    Omaha Omaha’s not much better.

    Slide 72

    Slide 72 text

    Unit testing JavaScript is hard when we write bad code Problem is, this is bad code. Any other programming language, we’d stop and refactor. Who was coding in something other than JS 5 years ago? Lots of us came to JS from other languages. We test there, right?

    Slide 73

    Slide 73 text

    Single Responsibility Principle Concept in OOP called the SRP Every module should have a single responsibility, and that responsibility should be entirely encapsulated by that module.

    Slide 74

    Slide 74 text

    jQuery(function($) { $('form').on('submit', function() { $.ajax({ url: '/statuses', type: 'POST', dataType: 'json', data: {text: $(this).find('textarea').val()}, success: function(data) { $('#statuses').append('
  • ' + data.text + '
  • '); } }); return false; }); }); Violates is so many ways Bound to page loading, so you only have one chance to test it Tied to form submission, so you have to do that Requires network activities, a textarea to have been filled in, an event triggered by successful ajax, and finally HTML templating.

    Slide 75

    Slide 75 text

    jQuery(function($) { $('form').on('submit', function() { $.ajax({ url: '/statuses', type: 'POST', dataType: 'json', data: {text: $(this).find('textarea').val()}, success: function(data) { $('#statuses').append('
  • ' + data.text + '
  • '); } }); return false; }); }); Violates is so many ways Bound to page loading, so you only have one chance to test it Tied to form submission, so you have to do that Requires network activities, a textarea to have been filled in, an event triggered by successful ajax, and finally HTML templating.

    Slide 76

    Slide 76 text

    jQuery(function($) { $('form').on('submit', function() { $.ajax({ url: '/statuses', type: 'POST', dataType: 'json', data: {text: $(this).find('textarea').val()}, success: function(data) { $('#statuses').append('
  • ' + data.text + '
  • '); } }); return false; }); }); Page Load Violates is so many ways Bound to page loading, so you only have one chance to test it Tied to form submission, so you have to do that Requires network activities, a textarea to have been filled in, an event triggered by successful ajax, and finally HTML templating.

    Slide 77

    Slide 77 text

    jQuery(function($) { $('form').on('submit', function() { $.ajax({ url: '/statuses', type: 'POST', dataType: 'json', data: {text: $(this).find('textarea').val()}, success: function(data) { $('#statuses').append('
  • ' + data.text + '
  • '); } }); return false; }); }); Page Load Form Event Violates is so many ways Bound to page loading, so you only have one chance to test it Tied to form submission, so you have to do that Requires network activities, a textarea to have been filled in, an event triggered by successful ajax, and finally HTML templating.

    Slide 78

    Slide 78 text

    jQuery(function($) { $('form').on('submit', function() { $.ajax({ url: '/statuses', type: 'POST', dataType: 'json', data: {text: $(this).find('textarea').val()}, success: function(data) { $('#statuses').append('
  • ' + data.text + '
  • '); } }); return false; }); }); Page Load Form Event Network I/O Violates is so many ways Bound to page loading, so you only have one chance to test it Tied to form submission, so you have to do that Requires network activities, a textarea to have been filled in, an event triggered by successful ajax, and finally HTML templating.

    Slide 79

    Slide 79 text

    jQuery(function($) { $('form').on('submit', function() { $.ajax({ url: '/statuses', type: 'POST', dataType: 'json', data: {text: $(this).find('textarea').val()}, success: function(data) { $('#statuses').append('
  • ' + data.text + '
  • '); } }); return false; }); }); Page Load Form Event Network I/O User Input Violates is so many ways Bound to page loading, so you only have one chance to test it Tied to form submission, so you have to do that Requires network activities, a textarea to have been filled in, an event triggered by successful ajax, and finally HTML templating.

    Slide 80

    Slide 80 text

    jQuery(function($) { $('form').on('submit', function() { $.ajax({ url: '/statuses', type: 'POST', dataType: 'json', data: {text: $(this).find('textarea').val()}, success: function(data) { $('#statuses').append('
  • ' + data.text + '
  • '); } }); return false; }); }); Page Load Form Event Network I/O Network Event User Input Violates is so many ways Bound to page loading, so you only have one chance to test it Tied to form submission, so you have to do that Requires network activities, a textarea to have been filled in, an event triggered by successful ajax, and finally HTML templating.

    Slide 81

    Slide 81 text

    jQuery(function($) { $('form').on('submit', function() { $.ajax({ url: '/statuses', type: 'POST', dataType: 'json', data: {text: $(this).find('textarea').val()}, success: function(data) { $('#statuses').append('
  • ' + data.text + '
  • '); } }); return false; }); }); Page Load Form Event Network I/O Network Event User Input Templating Violates is so many ways Bound to page loading, so you only have one chance to test it Tied to form submission, so you have to do that Requires network activities, a textarea to have been filled in, an event triggered by successful ajax, and finally HTML templating.

    Slide 82

    Slide 82 text

    6 Things to Test Only 14 lines of code. Dense, in the same way a ball of yarn is dense.

    Slide 83

    Slide 83 text

    6 Things to Change If any of them changes, all your tests fail. If all tests fail, you can’t know what’s caused a breakage

    Slide 84

    Slide 84 text

    6 Things to Fail At this point, might as well write integration/selenium tests. Not getting any benefit from them.

    Slide 85

    Slide 85 text

    jQuery(function($) { $('form').on('submit', function() { $.ajax({ url: '/statuses', type: 'POST', dataType: 'json', data: {text: $(this).find('textarea').val()}, success: function(data) { $('#statuses').append('
  • ' + data.text + '
  • '); } }); return false; }); }); Let’s return to the code. How could we fix it?

    Slide 86

    Slide 86 text

    var addStatus = function() { $.ajax({ url: '/statuses', type: 'POST', dataType: 'json', data: {text: $('textarea').val()}, success: function(data) { $('#statuses').append('
  • ' + data.text + '
  • '); } }); }; jQuery(function($) { $('form').on('submit', function() { addStatus(); return false; }); }); First step, separate ajax behvaior from document.ready. Moved into addStatus function

    Slide 87

    Slide 87 text

    var addStatus = function(options) { $.ajax({ url: '/statuses', type: 'POST', dataType: 'json', data: { text: options.text }, success: options.success }); }; jQuery(function($) { $('form').on('submit', function() { addStatus({ text: $('textarea').val(), success: function(data) { $('#statuses').append('
  • ' + data.text + '
  • '); } }); return false; }); }); Still having to do lots of DOM queries inside addStatus. Let’s have it be handed the text it should be sending, as well as the jQuery callback Now addStatus is solely focused on handing ajax, nothing else.

    Slide 88

    Slide 88 text

    Dependency Injection This idea is called DI. Probably have done it in Java or C#

    Slide 89

    Slide 89 text

    var Statuses = function() {}; Statuses.prototype.add = function(options) { $.ajax({ url: '/statuses', type: 'POST', dataType: 'json', data: { text: options.text }, success: options.success }); }; Let’s keep going. We can create a Model object Using design pattern called Constructor with Prototype Functions are objects; can now manage it. Easier to reuse model if we want to. Encapsulate data

    Slide 90

    Slide 90 text

    jQuery(function($) { var statuses = new Statuses(); $('form').on('submit', function() { statuses.add({ text: $('textarea').val(), success: function(data) { $('#statuses').append('
  • ' + data.text + '
  • '); } }); return false; }); }); Here’s what document.ready looks like. Newing up a Statuses model

    Slide 91

    Slide 91 text

    var NewStatusView = function(options) { var statuses = options.statuses; $('form').on('submit', function() { statuses.add({ text: $('textarea').val(), success: function(data) { $('#statuses').append('
  • ' + data.text + '
  • '); } }); return false; }); }; jQuery(function($) { var statuses = new Statuses(); new NewStatusView({statuses: statuses}); }); Still have lots inside page load. Let’s fix that Create a View object called NewStatusView Pass it our model object “statuses”. Now page load just assembles the elements together

    Slide 92

    Slide 92 text

    var NewStatusView = function(options) { this.statuses = options.statuses; $('form').on('submit', $.proxy(this.addStatus, this)); }; NewStatusView.prototype.addStatus = function(e) { e.preventDefault(); var that = this; this.statuses.add({ text: $('textarea').val(), success: function(data) { that.appendStatus(data.text); } }); return false; }; NewStatusView.prototype.appendStatus = function(text) { $('#statuses').append('
  • ' + text + '
  • '); }; We can structure the view more. Extract the HTML templating to appendStatus Move the addStatus code to its own function JS changes “this” inside member functions. Rules are confusing, we use $.proxy here

    Slide 93

    Slide 93 text

    describe('Monologue', function() { beforeEach(function() { this.server = sinon.fakeServer.create(); }); it('has a view with a model', function() { var statuses = new Statuses(), submitted = false; this.server.respondWith('{"text" : "Testing"}'); statuses.add({ text: 'Testing', success: function() { submitted = true; } }); this.server.respond(); expect(submitted).toBe(true); }); Here’s what a test looks like. Test our model. Fake out a server using Sinon Important code is where we new up Statuses and call Add. Totally isolated from the view. Can test the ajax behavior directly.

    Slide 94

    Slide 94 text

    describe('Status View', function() { it('is tied to Statuses', function() { var statuses = { add: function() {} }; var fixture = $('
    '); spyOn(statuses, 'add'); var newStatusView = new NewStatusView({ statuses: statuses, el: fixture }); fixture.find('form').trigger('submit'); expect(statuses.add).toHaveBeenCalled(); }); }); Testing our view is similar. We’re creating a fake version of our model Using Jasmine feature called spies to do this Can verify the view called Add on the spy Also: we’re not tied to document.ready. We test the view in isolation

    Slide 95

    Slide 95 text

    Let’s keep going Not a bad starting point. Should keep going. We expanded from 16 lines to 40. I think it’s better Each line is single responsibility. Could break it down further. Frameworks can help with this.

    Slide 96

    Slide 96 text

    var Monologue = { Model: {}, View: {}, Collection: {} }; Monologue.Model.Status = Backbone.Model.extend({}); Monologue.Collection.Statuses = Backbone.Collection.extend({ model: Monologue.Model.Status, url: '/statuses' }); Not a backbone class Lots of concepts apply. Example: Backbone has a Model with REST-style sync. Just specify collection and endpoint URL

    Slide 97

    Slide 97 text

    Monologue.View.PostStatus = Backbone.View.extend({ events: { 'submit' : 'submit' }, submit: function(e) { var $input = this.$el.find('textarea'); this.collection.create({text: $input.val()}); return false; } }); Views can say “on form submit, trigger submit code in view” You pass in the form, it’s responsible for creating a new collection based on data All totally encapsulated

    Slide 98

    Slide 98 text

    Monologue.View.StatusList = Backbone.View.extend({ initialize: function() { this.collection.on('add', this.add); this.collection.fetch(); }, add: function(model) { this.$el.append(this.template(model)); }, template: function(model) { return this.make('li', {'class':'status'}, model.get('text')); } }); Adding to collection triggers events Can add another view that’s bound to this collection. When items added, work gets done Templating is really straightforward too

    Slide 99

    Slide 99 text

    jQuery(function($) { var statuses = new Monologue.Collection.Statuses(); new Monologue.View.PostStatus({ el: $('#new-status'), collection: statuses }); new Monologue.View.StatusList({ el: $('#statuses'), collection: statuses }); }); Here’s how you set up your app. Again, just creating models and injecting views into our code

    Slide 100

    Slide 100 text

    describe('Post Status Form', function() { var view, $el, collection; beforeEach(function() { $el = $("Whee!"); collection = new (Backbone.Collection.extend({ url: '/mock' })); spyOn(collection, 'create'); view = new Monologue.View.PostStatus({ el: $el, collection: collection }); }); }); Here’s what testing the view looks like We create a view with a fake Backbone collection and textarea

    Slide 101

    Slide 101 text

    describe("submitting the form", function() { it("creates status when submitting the form", function() { $el.trigger('submit'); expect(collection.create) .toHaveBeenCalledWith({text: "Whee!"}); }); }); And by submitting the form, we verify the collection got a new element All the same concepts, now you’re just reducing the amount of code you have to write Testing models - way easier Can rely on dozens of tests from API. Don’t have to test ajax; they’ve done it!

    Slide 102

    Slide 102 text

    Putting your tests to work Will have a large test suite. Here’s how you make it work.

    Slide 103

    Slide 103 text

    LiveReload

    Slide 104

    Slide 104 text

    Grunt Has a test runner for Jasmine - in headless webkit Does way more; could spend an entire talk on it “Watch” - every time tests change, run

    Slide 105

    Slide 105 text

    Testacular Need to test in real browsers Testacular runs on everything you have, including PhantomJS Has Grunt plugin too, so can spin them up from CLI

    Slide 106

    Slide 106 text

    CI server Trending information Email when build fails No more broken builds

    Slide 107

    Slide 107 text

    Tools Code Tips The tools you use, how it changes the code you write, and some tips on how to get started

    Slide 108

    Slide 108 text

    1. Start Small Easier on greenfield projects Traditionally designed code = hard Start small side project & test from the beginning Do simple exercises. Project euler, code katas, etc

    Slide 109

    Slide 109 text

    2. Safety Net Refactoring Always get back to green

    Slide 110

    Slide 110 text

    3. TDD Write failing test Write production code Forces you to design so it’s testable I write all my code this way

    Slide 111

    Slide 111 text

    4. Synchronous first Jasmine can test async code But it’s tricky. Get your feet wet with sync code first Node is a good way to do this. Try out Node first!

    Slide 112

    Slide 112 text

    5. Pair Work with someone. Especially if you’re both new Keeps you honest, you learn how to make it work

    Slide 113

    Slide 113 text

    You Don’t Have to be Afraid Anymore Don’t have to be scared if you do these things. Can worry about other things, like browser perf, IE7, working on mobile, semicolons... well at least don’t have to worry about testing.

    Slide 114

    Slide 114 text

    fin @mattdsteele http://matthew-steele.com Questions? Will post slides here afterwards.