The new update can be downloaded on Android and iOS.
Twitter-owned Vine has rolled out a new messaging feature that enables users share video and text messages to any other users on the video app.
Adding both direct messaging channel and video clips to its messages, the new feature allows users to send videos to anyone, even if they are not in their personal address book.
Vine product manager Jason Toff said that the new feature came from understanding that there was a growing desire and need for private messaging on Vine.
"We've watched the community come up with some clever ways to send videos to their friends as we've been working on this solution," Toff added.
"Now you can privately send Vine videos and text messages to your friends. You can even send videos to anyone in your address book, even if they aren't on Vine."
The latest update follows Instagram's recent launch of a private messaging feature.


text:
Social Capital's Chamath Palihapitiya on Happiness, Startup Growth, and More – This Week in Startups with Jason Calacanis (Ep. 864)
Check out the This Week in Startups Episode Page & Show Notes
Watch the episode here
Make sure to pause and reflect from time to time and ask yourself – "Am I chasing someone else's definition of success, or my own?"
"Everyone wants to be validated and in the absence of being able to validate oneself from within, it's common to seek others to validate you"
~40% of every investment dollar is spent on Facebook/Google/Amazon ads
Too many founders and investors are looking for explosive growth which is causing an excess of capital in the VC world and driving artificial startup growth that just isn't sustainable
Even if a startup doesn't make an exit, VC partners still make millions from fees – so their motive may not be aligned with the founder's
Chamath's advice to companies – "Grow real, grow slow"
A great quote for founders – "You guys are trying to build things, and that's so hard, and nobody believes in you. It's so dark and you're all alone. The last thing you want is someone telling you that you can't do it. F*ck those people."
Chamath Palihapitiya (@chamath) is a venture capitalist and the founder and CEO of Social Capital
He was an early senior executive at Facebook, joining the company in 2007 and leaving in 2011
Chamath is also an owner and board member of the NBA's Golden State Warriors
Money Doesn't Equal Happiness
Chamath says although he was richer than ever before last year, he just wasn't happy with his life
Why? – He wasn't passionate about the business he was building
Chamath says he also had unresolved trauma from his childhood – he grew up with an alcoholic and abusive father
"I felt zero, zero, zero, sense of worth. And so I chased everybody else's definition of what worthiness and success should mean."
Building Your Dream vs Somebody Else's
Recently, Chamath realized that part of his professional unhappiness came from building someone else's company and not his own
Something to ask yourself – "Am I building the thing that I want or am I building the thing that someone else wants?"
Sometimes people want to become founders, but the temptation of getting paid large sums of money to build someone else's product is too strong and people put their dreams on hold
"Everyone wants to be validated and in the absence of being able to validate oneself from within, you seek others to validate you"
Making The RIGHT Investments
When reflecting on his investments, Chamath saw that although the returns were "bananas," he felt his company (Social Capital) wasn't focusing enough on having a positive impact
"What we are doing today is an attempt to get back to where we started and where we should have always been, which is solving very difficult problems that if solved, matter in the world"
"When you measure and quantify the absolute value of progress, it's way more enormous than measuring the absolute value of discounting cash flows"
Chamath plans to put in $2 billion of his own capital into the Social Capital fund
"We are investing as much as we need, for as long as we want, in the things that are important"
The Similarities Between VC Funding and Ponzi Schemes
Chamath's example: A startup raises a Series A round, and the investors want the company to grow as fast as possible so they tell the startup to focus on growth. However, raising money doesn't equal a better product/market fit. So how can the startup just go off and grow quickly?
Facebook, Google and Amazon ads and other services – "40% of every investment dollar goes to these companies"
Then Series B investors then see the growth of this startup and invest in them. Now, these investors want to see more growth. So once again, the company starts spending its investment money on Facebook, Google and Amazon ads.
The cycle repeats over and over again, creating the illusion of successful growth on paper
Meanwhile, the Series A and B investors start to think venture funding is easy, so they go out and start their own funds
This creates an excess of capital (supply) in the market and gives startups more leverage to ask for more capital for less equity, creating lower returns for all VC funds
Meanwhile, the partners at these funds collect fees whether or not they are able to generate a large return (Chamath says that a partner, on average, can make $1 million a year from fees alone)
"The incentives in this industry are the most out of whack they've ever been. It's a bit of a charade."
This is why Chamath is waiting for "some of the air to get out of this balloon" before making more investments
"I think the charade is dangerous and particularly dangerous to these companies"
Chamath's advice – "Grow real, grow slow."
Look at Amazon – slow consistent growth over decades that has created a $1 trillion market cap company
When investing, think of a graph with Capital Heavy to Capital Light on one axis and Obvious and Nonobvious on the other
Most investors want obvious and capital light investments (Ex. – The many scooter startups across the U.S.)
However, Chamath prefers capital heavy and nonobvious investments (Ex. – Elon Musk with rocket ships and electric cars)
Ignore the Critics
"Nobody remembers a critic"
"You guys are trying to build things, and that's so hard, and nobody believes in you. It's so dark and you're all alone. The last thing you want is someone telling you that you can't do it. F*ck those people."
"You're on the field doing things, be proud. That's amazing, that's hard."
This Week in Startups with Jason Calacanis : entrepreneur, entrepreneurship, investing, startup, Venture capital
Notes By Alex Wiec
Morgan Housel: Happiness, Fame, Reading, ChatGPT & Optimism | The Danny Miranda Podcast 299
Morgan Housel and Danny Miranda discuss why 90% of virality is luck, obsession, Morgan's research process, choosing what to read, how to...
Blake Scholl: How Boom Supersonic Took Flight | Starting Greatness With Mike Maples, Jr
Blake Scholl and Mike Maples, Jr discuss why and how Blake started Boom Supersonic, the state of the commercial air travel industry, why...
MFM Mini-Camp, Lessons from Selling a Company, and Storytelling Wins | My First Million Podcast with Shaan Puri and Sam Parr #405
Sam Parr and Shaan Puri discuss the party that Shaan threw with San Diego's most interesting millionaires, lessons learned from the...

redpajama_set_name: RedPajamaCommonCrawl


Pathlab endeavours to provide the best quality care at all times, however if you have a problem or concern about the service you receive, please tell us. To make a complaint you can telephone, email, or write to us. Our contact details are here.


text:
Music and Poetry, SS Lesson - Doctrine and Covenants
Literary DCGD #1: On the Latter-day Dispensation
by Kent Larsen • December 30, 2012 • 2 Comments
The initial lesson in the Doctrine and Covenants and Church History course of study points out that the revelations found in the text are meant for our time and cover our dispensation, while the history presented is the history of our people, as opposed to those who lived aeons ago. This course should, therefore, be relevant to us today in a way that the other Gospel Doctrine courses can't hope to accomplish.
The poem below discusses not only a few of the major events that opened our dispensation, but also follows the prediction often made; that our dispensation has a great destiny leading to the coming of our Lord.
The author of this poem, James H. Hart (1825-1906) was a prominent member of what might be called the second generation of Mormonism, and among the first generation of immigrants from outside the U.S. Born in Birmingham, England, Hart joined the Church there in 1847. He served several short missions in the British Isles in the ensuing years before serving a mission to France in the early 1850s. In 1855 he immigrated to the U.S., and served as President of the short-lived original St. Louis Stake until 1857, while managing the Church's periodical there, the St. Louis Luminary. He lived in Salt Lake from 1857 to 1864, when he moved to Bloomington, Idaho (Bear Lake County), becoming first the postmaster, then a county probate Judge in 1870. He also represented the area in the Idaho State Legislature for six years. For most of the 1880s Hart also served as the Church's immigration agent in New York City, assisting tens of thousands of immigrants through U.S. immigration procedures and on their way to Utah. Hart was also a frequent contributor of poetry and other material to the Church magazines.
On the Latter-day Dispensation
by James H. Hart
When Zion's long appointed time, foretold in sacred pages,
Had come, by faith and promise, as looked for through the ages,
Then came the Eternal Father, in plenitude of power,
And revealed Himself in person, in this the eleventh hour.
His royal Son was present on that eventful day,
And pointing, said, "Behold my Son, hear Him and Him obey."
Thus spake the great Jehovah, as man would speak to man,
And thus the Church of Jesus Christ in latter-days began.
Not only these great Potentates of everlasting fame,
But Moses and Elijah and other Prophets came.
Thus blending frail humanity, with all the name implies,
With Rulers of the Universe, and Legates of the skies.
Three witnesses were chosen, and unto them was given
To see the great Moroni, a messenger from heaven,
Who showed them the engravings, and plates of golden hue,
And bade them handle, and to bear a record, strange, but true.
As they were so directed, they saw, and felt, and knew,
The records and the messenger were genuine and true.
Nor did they ever waver midst all the scenes of strife,
But testified persistently throughout their checkered life.
The honored Seer was but a boy-had seen but fourteen years-
To whom the future was portrayed through both the hemispheres;
'Twas shown that 'mongst the nations he would form a brotherhood,
His name be known in every land for evil and for good.
They told him not to fraternize with any sect or creed,
That all were vague and spurious, and led by selfish greed;
They had neither Faith nor Priesthood, nor Gospel that could save
Themselves and those that slumber in dark and silent grave.
They gave the tyro keys and powers to formulate the scheme
Devised to found a Kingdom and every soul redeem;
E'en every son of Adam who would subjugate his will,
In this or in the spirit world, and righteous laws fulfil.
The record he translated by gift, and power, and grace
Of God, th' Eternal Father of all the human race.
And those who will may read and learn the history sublime
Of nations on this continent, down from remotest time.
He organized the Church of Christ as 'twas in days of yore,
Its officers and rites complete, no fewer, and no more;
Its faith and gifts and miracles were thus restored again,
And with the Church of latter days forever will remain.
He taught mankind to worship the true and living God,
Who in the form of perfect man the earth again has trod.
He taught the only Gospel the Gods have ever given,
To elevate the sons of men from earth to reign in heaven.
Among our great reformers he stands without a peer,
Combining all the qualities of Prophet, Priest and Seer;
And in the role of statesman prescribed a plan to save
Thousands who now lie mouldering in fratricidal grave.
But every word and prophecy were backed with vital power,
His work has been triumphant in every trying hour;
With this eternal Priesthood there's no such thing as fail,
For, guided by Omnipotence, they must and will prevail.
They grapple with emergencies and breast the surging wave,
And some o'er death have triumphed, and rest in silent grave;
They're uncompromising heroes, 'mid scenes of Gentile strife,
And fight for truth and liberty in jeopardy of life.
No greater revolution on earth was e'er begun;
More splendid victories for truth the Prophets never won.
They've gathered ransomed legions from far and distant lands,
Dispelled the fogs of ages, and broke the tyrant bands.
And this is but commencement, a beginning of the plan,
Projected in eternity before the world began,
To found a righteous Kingdom, whose power will never end,
Where every man will meet in peace a brother and a friend.
The Contributor 4 N10, July 1883
Many lines in this poem stuck out to me for what they say. I like Hart's claim about the visits from heaven to Joseph Smith:
Indeed, one of the fascinating facets of Mormonism is its blending of the divine with the mortal, the shrinking of the distance between heaven and earth.
Another insightful line comes in his description of the three witnesses:
Indeed, their lives were "checkered" and filled with "strife!"
Hart was not without admiration of those of the first generation, revering them with the lines:
Indeed, he presents an admiration that is common in many current descriptions of the "pioneers," those who displayed "faith in every footstep." I can easily imagine this applied to the often hallowed Willie and Martin handcart companies.
I should probably point out that in the first line of the eighth stanza, "They gave the tyro keys and powers…" the word "tyro" is not an error, but is a rarely used word meaning novice or beginner.
But perhaps the most relevant lines in the poem are these:
Indeed, teaching this is, I think, the point of these lessons.
Tags: Church History doctrine and covenants gospel doctrine James H. Hart Latter-day Dispensation poetry
← The Council in the Preexistence (medieval Antichrist edition)
Resolved: →
2 comments for "Literary DCGD #1: On the Latter-day Dispensation"
Hart interviewed David Whitmer in Missouri in August 1883 and obtained a daguerreotype of Oliver Cowdery from Whitmer. He wrote a poem entitled "Interview With David Whitmer" which was published in the Contributor in October 1883. See "Mormon in Motion," Edward Hart (1978).
I may have conflated the two events. He interviewed Whitmer and obtained the daguerreotype, but I don't think he obtained the daguerreotype from Whitmer,


"A girl's gotta do what a girl's gotta do!" That's what it says on this Betty Boop Girl's Gotta Do Stepping Stone featuring the ever-popular boop-oop-a-doop girl. Guess you gotta order one, huh? Measures 9 1/2-inches long x 9 1/2 wide x 1/2-inch thick, and weighs 2 3/4-pounds.


text:
static NSTimeInterval const kUpdateTimeout = 1.0;

@interface BasicCollectionDataSource : MUKDataSource
@end

@implementation BasicCollectionDataSource

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    [collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"Cell"];
    return [collectionView dequeueReusableCellWithReuseIdentifier:@"Cell" forIndexPath:indexPath];
}

- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath
{
    [collectionView registerClass:[UICollectionReusableView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"Header"];
    return [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:@"Header" forIndexPath:indexPath];
}

@end

#pragma mark -

@interface MUKDataSourceCollectionUpdateTests : XCTestCase
@end

@implementation MUKDataSourceCollectionUpdateTests

- (void)testSectionInsertion {
    NSArray *const sourceSections = @[ CollectionSection(@"a", nil, nil), CollectionSection(@"b", nil, nil) ];
    NSArray *const destinationSections = @[ CollectionSection(@"a", nil, nil), CollectionSection(@"c", nil, nil), CollectionSection(@"b", nil, nil) ];

    MUKDataSource *const dataSource = [[BasicCollectionDataSource alloc] init];
    UICollectionView *const collectionView = [self newCollectionViewWithSections:sourceSections dataSource:dataSource];
    XCTAssertEqual([collectionView numberOfSections], sourceSections.count);

    MUKDataSourceCollectionUpdate *const update = [dataSource setCollectionSections:destinationSections];

    XCTestExpectation *expectation = [self expectationWithDescription:@"Update finished"];
    XCTAssertNoThrow([update applyToCollectionView:collectionView completion:^(BOOL finished)
    {
        [expectation fulfill];
    }]);
    [self waitForExpectationsWithTimeout:kUpdateTimeout handler:nil];

    XCTAssertFalse(update.needsReloadData);
    XCTAssertEqual([collectionView numberOfSections], destinationSections.count);
}

- (void)testSectionDeletion {
    NSArray *const sourceSections = @[ CollectionSection(@"a", nil, nil), CollectionSection(@"b", nil, nil), CollectionSection(@"c", nil, nil), CollectionSection(@"d", nil, nil) ];
    NSArray *const destinationSections = @[ CollectionSection(@"a", nil, nil), CollectionSection(@"d", nil, nil) ];

    MUKDataSource *const dataSource = [[BasicCollectionDataSource alloc] init];
    UICollectionView *const collectionView = [self newCollectionViewWithSections:sourceSections dataSource:dataSource];
    XCTAssertEqual([collectionView numberOfSections], sourceSections.count);

    MUKDataSourceCollectionUpdate *const update = [dataSource setCollectionSections:destinationSections];

    XCTestExpectation *expectation = [self expectationWithDescription:@"Update finished"];
    XCTAssertNoThrow([update applyToCollectionView:collectionView completion:^(BOOL finished)
    {
        [expectation fulfill];
    }]);
    [self waitForExpectationsWithTimeout:kUpdateTimeout handler:nil];

    XCTAssertFalse(update.needsReloadData);
    XCTAssertEqual([collectionView numberOfSections], destinationSections.count);
}

- (void)testSectionReload {
    // a, b, c
    // a, b', c'
    NSArray *const sourceSections = @[ CollectionSection(@"a", nil, nil), CollectionSection(@"b", nil, nil), CollectionSection(@"c", nil, nil) ];
    NSArray *const destinationSections = @[ CollectionSection(@"a", nil, nil), CollectionSection(@"b", @"B'", nil), CollectionSection(@"c", @"C'", nil) ];

    MUKDataSource *const dataSource = [[BasicCollectionDataSource alloc] init];
    UICollectionView *const collectionView = [self newCollectionViewWithSections:sourceSections dataSource:dataSource];
    XCTAssertEqual([collectionView numberOfSections], sourceSections.count);

    MUKDataSourceCollectionUpdate *const update = [dataSource setCollectionSections:destinationSections];

    NSIndexSet *const reloadedSections = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, 2)];

    XCTAssertEqual(update.insertedSectionIndexes.count, 0);
    XCTAssertEqual(update.deletedSectionIndexes.count, 0);
    XCTAssertEqualObjects(update.reloadedSectionIndexes, reloadedSections);
    XCTAssertEqual(update.sectionMovements.count, 0);

    XCTAssertEqual(update.insertedItemIndexPaths.count, 0);
    XCTAssertEqual(update.deletedItemIndexPaths.count, 0);
    XCTAssertEqual(update.reloadedItemIndexPaths.count, 0);
    XCTAssertEqual(update.itemMovements.count, 0);

    XCTestExpectation *expectation = [self expectationWithDescription:@"Update finished"];
    XCTAssertNoThrow([update applyToCollectionView:collectionView completion:^(BOOL finished)
    {
        [expectation fulfill];
    }]);
    [self waitForExpectationsWithTimeout:kUpdateTimeout handler:nil];

    XCTAssertFalse(update.needsReloadData);
    XCTAssertEqual([collectionView numberOfSections], destinationSections.count);
}

- (void)testSectionMovements {
    NSArray *const sourceSections = @[ CollectionSection(@"a", nil, nil), CollectionSection(@"b", nil, nil), CollectionSection(@"c", nil, nil), CollectionSection(@"d", nil, nil) ];
    NSArray *const destinationSections = @[ CollectionSection(@"c", nil, nil), CollectionSection(@"b", nil, nil), CollectionSection(@"d", nil, nil), CollectionSection(@"a", nil, nil) ];

    MUKDataSource *const dataSource = [[BasicCollectionDataSource alloc] init];
    UICollectionView *const collectionView = [self newCollectionViewWithSections:sourceSections dataSource:dataSource];
    XCTAssertEqual([collectionView numberOfSections], sourceSections.count);

    MUKDataSourceCollectionUpdate *const update = [dataSource setCollectionSections:destinationSections];

    XCTestExpectation *expectation = [self expectationWithDescription:@"Update finished"];
    XCTAssertNoThrow([update applyToCollectionView:collectionView completion:^(BOOL finished)
    {
        [expectation fulfill];
    }]);
    [self waitForExpectationsWithTimeout:kUpdateTimeout handler:nil];

    XCTAssertFalse(update.needsReloadData);
    XCTAssertEqual([collectionView numberOfSections], destinationSections.count);
}

- (void)testSectionComboInsertionDeletion {
    NSArray *const sourceSections = @[ CollectionSection(@"a", nil, nil), CollectionSection(@"b", nil, nil) ];
    NSArray *const destinationSections = @[ CollectionSection(@"c", nil, nil), CollectionSection(@"b", nil, nil) ];

    MUKDataSource *const dataSource = [[BasicCollectionDataSource alloc] init];
    UICollectionView *const collectionView = [self newCollectionViewWithSections:sourceSections dataSource:dataSource];
    XCTAssertEqual([collectionView numberOfSections], sourceSections.count);

    MUKDataSourceCollectionUpdate *const update = [dataSource setCollectionSections:destinationSections];

    XCTestExpectation *expectation = [self expectationWithDescription:@"Update finished"];
    XCTAssertNoThrow([update applyToCollectionView:collectionView completion:^(BOOL finished)
    {
        [expectation fulfill];
    }]);
    [self waitForExpectationsWithTimeout:kUpdateTimeout handler:nil];

    XCTAssertFalse(update.needsReloadData);
    XCTAssertEqual([collectionView numberOfSections], destinationSections.count);
}

- (void)testSectionComboInsertionReload {
    // a, b
    // c, d, a, e, b', f
    NSArray *const sourceSections = @[ CollectionSection(@"a", nil, nil), CollectionSection(@"b", nil, nil) ];
    NSArray *const destinationSections = @[ CollectionSection(@"c", nil, nil), CollectionSection(@"d", nil, nil), CollectionSection(@"a", nil, nil), CollectionSection(@"e", nil, nil), CollectionSection(@"b", @"B'", nil), CollectionSection(@"f", nil, nil) ];

    MUKDataSource *const dataSource = [[BasicCollectionDataSource alloc] init];
    UICollectionView *const collectionView = [self newCollectionViewWithSections:sourceSections dataSource:dataSource];
    XCTAssertEqual([collectionView numberOfSections], sourceSections.count);

    MUKDataSourceCollectionUpdate *const update = [dataSource setCollectionSections:destinationSections];

    NSMutableIndexSet *const insertedSections = [NSMutableIndexSet indexSet];
    [insertedSections addIndex:0];
    [insertedSections addIndex:1];
    [insertedSections addIndex:3];
    [insertedSections addIndex:5];
    NSIndexSet *const reloadedSections = [NSIndexSet indexSetWithIndex:4];

    XCTAssertEqualObjects(update.insertedSectionIndexes, insertedSections);
    XCTAssertEqual(update.deletedSectionIndexes.count, 0);
    XCTAssertEqualObjects(update.reloadedSectionIndexes, reloadedSections);
    XCTAssertEqual(update.sectionMovements.count, 0);

    XCTAssertEqual(update.insertedItemIndexPaths.count, 0);
    XCTAssertEqual(update.deletedItemIndexPaths.count, 0);
    XCTAssertEqual(update.reloadedItemIndexPaths.count, 0);
    XCTAssertEqual(update.itemMovements.count, 0);

    XCTestExpectation *expectation = [self expectationWithDescription:@"Update finished"];
    XCTAssertNoThrow([update applyToCollectionView:collectionView completion:^(BOOL finished)
    {
        [expectation fulfill];
    }]);
    [self waitForExpectationsWithTimeout:kUpdateTimeout handler:nil];

    XCTAssertFalse(update.needsReloadData);
    XCTAssertEqual([collectionView numberOfSections], destinationSections.count);
}

- (void)testSectionComboInsertionMovements {
    // a, b, c, d
    // e, c, b, d, f, a
    NSArray *const sourceSections = @[ CollectionSection(@"a", nil, nil), CollectionSection(@"b", nil, nil), CollectionSection(@"c", nil, nil), CollectionSection(@"d", nil, nil) ];
    NSArray *const destinationSections = @[ CollectionSection(@"e", nil, nil), CollectionSection(@"c", nil, nil), CollectionSection(@"b", nil, nil), CollectionSection(@"d", nil, nil), CollectionSection(@"f", nil, nil), CollectionSection(@"a", nil, nil) ];

    MUKDataSource *const dataSource = [[BasicCollectionDataSource alloc] init];
    UICollectionView *const collectionView = [self newCollectionViewWithSections:sourceSections dataSource:dataSource];
    XCTAssertEqual([collectionView numberOfSections], sourceSections.count);

    MUKDataSourceCollectionUpdate *const update = [dataSource setCollectionSections:destinationSections];

    XCTestExpectation *expectation = [self expectationWithDescription:@"Update finished"];
    XCTAssertNoThrow([update applyToCollectionView:collectionView completion:^(BOOL finished)
    {
        [expectation fulfill];
    }]);
    [self waitForExpectationsWithTimeout:kUpdateTimeout handler:nil];

    XCTAssertFalse(update.needsReloadData);
    XCTAssertEqual([collectionView numberOfSections], destinationSections.count);
}

- (void)testSectionComboDeletionReload {
    // a, b, c, d
    // b, d'
    NSArray *const sourceSections = @[ CollectionSection(@"a", nil, nil), CollectionSection(@"b", nil, nil), CollectionSection(@"c", nil, nil), CollectionSection(@"d", nil, nil) ];
    NSArray *const destinationSections = @[ CollectionSection(@"b", nil, nil), CollectionSection(@"d", @"D'", nil) ];

    MUKDataSource *const dataSource = [[BasicCollectionDataSource alloc] init];
    UICollectionView *const collectionView = [self newCollectionViewWithSections:sourceSections dataSource:dataSource];
    XCTAssertEqual([collectionView numberOfSections], sourceSections.count);

    MUKDataSourceCollectionUpdate *const update = [dataSource setCollectionSections:destinationSections];

    NSMutableIndexSet *const deletedSections = [NSMutableIndexSet indexSetWithIndex:0];
    [deletedSections addIndex:2];
    NSIndexSet *const reloadedSections = [NSIndexSet indexSetWithIndex:1];

    XCTAssertEqual(update.insertedSectionIndexes.count, 0);
    XCTAssertEqualObjects(update.deletedSectionIndexes, deletedSections);
    XCTAssertEqualObjects(update.reloadedSectionIndexes, reloadedSections);
    XCTAssertEqual(update.sectionMovements.count, 0);

    XCTAssertEqual(update.insertedItemIndexPaths.count, 0);
    XCTAssertEqual(update.deletedItemIndexPaths.count, 0);
    XCTAssertEqual(update.reloadedItemIndexPaths.count, 0);
    XCTAssertEqual(update.itemMovements.count, 0);

    XCTestExpectation *expectation = [self expectationWithDescription:@"Update finished"];
    XCTAssertNoThrow([update applyToCollectionView:collectionView completion:^(BOOL finished)
    {
        [expectation fulfill];
    }]);
    [self waitForExpectationsWithTimeout:kUpdateTimeout handler:nil];

    XCTAssertFalse(update.needsReloadData);
    XCTAssertEqual([collectionView numberOfSections], destinationSections.count);
}

- (void)testSectionComboDeletionMovements {
    // a, b, c, d, e
    // b, e, c
    NSArray *const sourceSections = @[ CollectionSection(@"a", nil, nil), CollectionSection(@"b", nil, nil), CollectionSection(@"c", nil, nil), CollectionSection(@"d", nil, nil), CollectionSection(@"e", nil, nil) ];
    NSArray *const destinationSections = @[ CollectionSection(@"b", nil, nil), CollectionSection(@"e", nil, nil), CollectionSection(@"c", nil, nil) ];

    MUKDataSource *const dataSource = [[BasicCollectionDataSource alloc] init];
    UICollectionView *const collectionView = [self newCollectionViewWithSections:sourceSections dataSource:dataSource];
    XCTAssertEqual([collectionView numberOfSections], sourceSections.count);

    MUKDataSourceCollectionUpdate *const update = [dataSource setCollectionSections:destinationSections];

    XCTestExpectation *expectation = [self expectationWithDescription:@"Update finished"];
    XCTAssertNoThrow([update applyToCollectionView:collectionView completion:^(BOOL finished)
    {
        [expectation fulfill];
    }]);
    [self waitForExpectationsWithTimeout:kUpdateTimeout handler:nil];

    XCTAssertFalse(update.needsReloadData);
    XCTAssertEqual([collectionView numberOfSections], destinationSections.count);
}

- (void)testSectionComboReloadMovements {
    // a, b, c
    // c, b, a'
    NSArray *const sourceSections = @[ CollectionSection(@"a", nil, nil), CollectionSection(@"b", nil, nil), CollectionSection(@"c", nil, nil) ];
    NSArray *const destinationSections = @[ CollectionSection(@"c", nil, nil), CollectionSection(@"b", nil, nil), CollectionSection(@"a", @"A'", nil) ];

    MUKDataSource *const dataSource = [[BasicCollectionDataSource alloc] init];
    UICollectionView *const collectionView = [self newCollectionViewWithSections:sourceSections dataSource:dataSource];
    XCTAssertEqual([collectionView numberOfSections], sourceSections.count);

    MUKDataSourceCollectionUpdate *const update = [dataSource setCollectionSections:destinationSections];
    NSIndexSet *const reloadedSections = [NSIndexSet indexSetWithIndex:2];

    // 0->2, 2->0
    NSSet *const movements = [NSSet setWithObjects:[[MUKDataSourceContentSectionMovement alloc] initWithSourceIndex:0 destinationIndex:2], [[MUKDataSourceContentSectionMovement alloc] initWithSourceIndex:2 destinationIndex:0], nil];

    XCTAssertEqual(update.insertedSectionIndexes.count, 0);
    XCTAssertEqual(update.deletedSectionIndexes.count, 0);
    XCTAssertEqualObjects(update.reloadedSectionIndexes, reloadedSections);
    XCTAssertEqualObjects(update.sectionMovements, movements);

    XCTAssertEqual(update.insertedItemIndexPaths.count, 0);
    XCTAssertEqual(update.deletedItemIndexPaths.count, 0);
    XCTAssertEqual(update.reloadedItemIndexPaths.count, 0);
    XCTAssertEqual(update.itemMovements.count, 0);

    XCTestExpectation *expectation = [self expectationWithDescription:@"Update finished"];
    XCTAssertNoThrow([update applyToCollectionView:collectionView completion:^(BOOL finished)
    {
        [expectation fulfill];
    }]);
    [self waitForExpectationsWithTimeout:kUpdateTimeout handler:nil];

    XCTAssertFalse(update.needsReloadData);
    XCTAssertEqual([collectionView numberOfSections], destinationSections.count);
}

- (void)testSectionComboInsertionDeletionReload {
    // a, b, c
    // a, d, b', e
    NSArray *const sourceSections = @[ CollectionSection(@"a", nil, nil), CollectionSection(@"b", nil, nil), CollectionSection(@"c", nil, nil) ];
    NSArray *const destinationSections = @[ CollectionSection(@"a", nil, nil), CollectionSection(@"d", nil, nil), CollectionSection(@"b", @"B'", nil), CollectionSection(@"e", nil, nil) ];

    MUKDataSource *const dataSource = [[BasicCollectionDataSource alloc] init];
    UICollectionView *const collectionView = [self newCollectionViewWithSections:sourceSections dataSource:dataSource];
    XCTAssertEqual([collectionView numberOfSections], sourceSections.count);

    MUKDataSourceCollectionUpdate *const update = [dataSource setCollectionSections:destinationSections];

    NSMutableIndexSet *const insertedSections = [NSMutableIndexSet indexSetWithIndex:1];
    [insertedSections addIndex:3];
    NSIndexSet *const deletedSections = [NSIndexSet indexSetWithIndex:2];
    NSIndexSet *const reloadedSections = [NSIndexSet indexSetWithIndex:2];

    XCTAssertEqualObjects(update.insertedSectionIndexes, insertedSections);
    XCTAssertEqualObjects(update.deletedSectionIndexes, deletedSections);
    XCTAssertEqualObjects(update.reloadedSectionIndexes, reloadedSections);
    XCTAssertEqual(update.sectionMovements.count, 0);

    XCTAssertEqual(update.insertedItemIndexPaths.count, 0);
    XCTAssertEqual(update.deletedItemIndexPaths.count, 0);
    XCTAssertEqual(update.reloadedItemIndexPaths.count, 0);
    XCTAssertEqual(update.itemMovements.count, 0);

    XCTestExpectation *expectation = [self expectationWithDescription:@"Update finished"];
    XCTAssertNoThrow([update applyToCollectionView:collectionView completion:^(BOOL finished)
    {
        [expectation fulfill];
    }]);
    [self waitForExpectationsWithTimeout:kUpdateTimeout handler:nil];

    XCTAssertFalse(update.needsReloadData);
    XCTAssertEqual([collectionView numberOfSections], destinationSections.count);
}

- (void)testSectionComboInsertionDeletionMovement {
    // a, b, c
    // b, d, a, e
    NSArray *const sourceSections = @[ CollectionSection(@"a", nil, nil), CollectionSection(@"b", nil, nil), CollectionSection(@"c", nil, nil) ];
    NSArray *const destinationSections = @[ CollectionSection(@"b", nil, nil), CollectionSection(@"d", nil, nil), CollectionSection(@"a", nil, nil), CollectionSection(@"e", nil, nil) ];

    MUKDataSource *const dataSource = [[BasicCollectionDataSource alloc] init];
    UICollectionView *const collectionView = [self newCollectionViewWithSections:sourceSections dataSource:dataSource];
    XCTAssertEqual([collectionView numberOfSections], sourceSections.count);

    MUKDataSourceCollectionUpdate *const update = [dataSource setCollectionSections:destinationSections];

    XCTestExpectation *expectation = [self expectationWithDescription:@"Update finished"];
    XCTAssertNoThrow([update applyToCollectionView:collectionView completion:^(BOOL finished)
    {
        [expectation fulfill];
    }]);
    [self waitForExpectationsWithTimeout:kUpdateTimeout handler:nil];

    XCTAssertFalse(update.needsReloadData);
    XCTAssertEqual([collectionView numberOfSections], destinationSections.count);
}

- (void)testSectionComboDeletionReloadMovement {
    // a, b, c, d
    // b, a', d
    NSArray *const sourceSections = @[ CollectionSection(@"a", @"a", nil), CollectionSection(@"b", nil, nil), CollectionSection(@"c", nil, nil), CollectionSection(@"d", nil, nil) ];
    NSArray *const destinationSections = @[ CollectionSection(@"b", nil, nil), CollectionSection(@"a", @"a'", nil), CollectionSection(@"d", nil, nil) ];

    MUKDataSource *const dataSource = [[BasicCollectionDataSource alloc] init];
    UICollectionView *const collectionView = [self newCollectionViewWithSections:sourceSections dataSource:dataSource];
    XCTAssertEqual([collectionView numberOfSections], sourceSections.count);

    MUKDataSourceCollectionUpdate *const update = [dataSource setCollectionSections:destinationSections];

    NSIndexSet *const deletedSections = [NSIndexSet indexSetWithIndex:2];
    NSIndexSet *const reloadedSections = [NSIndexSet indexSetWithIndex:1];

    NSSet *const movements = [NSSet setWithObjects:[[MUKDataSourceContentSectionMovement alloc] initWithSourceIndex:0 destinationIndex:1], nil];

    XCTAssertEqual(update.insertedSectionIndexes.count, 0);
    XCTAssertEqualObjects(update.deletedSectionIndexes, deletedSections);
    XCTAssertEqualObjects(update.reloadedSectionIndexes, reloadedSections);
    XCTAssertEqualObjects(update.sectionMovements, movements);

    XCTAssertEqual(update.insertedItemIndexPaths.count, 0);
    XCTAssertEqual(update.deletedItemIndexPaths.count, 0);
    XCTAssertEqual(update.reloadedItemIndexPaths.count, 0);
    XCTAssertEqual(update.itemMovements.count, 0);

    XCTestExpectation *expectation = [self expectationWithDescription:@"Update finished"];
    XCTAssertNoThrow([update applyToCollectionView:collectionView completion:^(BOOL finished)
    {
        [expectation fulfill];
    }]);
    [self waitForExpectationsWithTimeout:kUpdateTimeout handler:nil];

    XCTAssertFalse(update.needsReloadData);
    XCTAssertEqual([collectionView numberOfSections], destinationSections.count);
}

- (void)testSectionComboInsertionDeletionReloadMovement {
    // a, b, c
    // b, d, a', e
    NSArray *const sourceSections = @[ CollectionSection(@"a", nil, nil), CollectionSection(@"b", nil, nil), CollectionSection(@"c", nil, nil) ];
    NSArray *const destinationSections = @[ CollectionSection(@"b", nil, nil), CollectionSection(@"d", nil, nil), CollectionSection(@"a", @"A'", nil), CollectionSection(@"e", nil, nil) ];

    MUKDataSource *const dataSource = [[BasicCollectionDataSource alloc] init];
    UICollectionView *const collectionView = [self newCollectionViewWithSections:sourceSections dataSource:dataSource];
    XCTAssertEqual([collectionView numberOfSections], sourceSections.count);

    MUKDataSourceCollectionUpdate *const update = [dataSource setCollectionSections:destinationSections];

    NSMutableIndexSet *const insertedSections = [NSMutableIndexSet indexSetWithIndex:1];
    [insertedSections addIndex:3];
    NSIndexSet *const deletedSections = [NSIndexSet indexSetWithIndex:2];
    NSIndexSet *const reloadedSections = [NSIndexSet indexSetWithIndex:2];

    NSSet *const movements = [NSSet setWithObjects:[[MUKDataSourceContentSectionMovement alloc] initWithSourceIndex:0 destinationIndex:2], nil];

    XCTAssertEqualObjects(update.insertedSectionIndexes, insertedSections);
    XCTAssertEqualObjects(update.deletedSectionIndexes, deletedSections);
    XCTAssertEqualObjects(update.reloadedSectionIndexes, reloadedSections);
    XCTAssertEqualObjects(update.sectionMovements, movements);

    XCTAssertEqual(update.insertedItemIndexPaths.count, 0);
    XCTAssertEqual(update.deletedItemIndexPaths.count, 0);
    XCTAssertEqual(update.reloadedItemIndexPaths.count, 0);
    XCTAssertEqual(update.itemMovements.count, 0);

    XCTestExpectation *expectation = [self expectationWithDescription:@"Update finished"];
    XCTAssertNoThrow([update applyToCollectionView:collectionView completion:^(BOOL finished)
    {
        [expectation fulfill];
    }]);
    [self waitForExpectationsWithTimeout:kUpdateTimeout handler:nil];

    XCTAssertFalse(update.needsReloadData);
    XCTAssertEqual([collectionView numberOfSections], destinationSections.count);
}

- (void)testNotReloadedChangedSection {
    // a, b, c
    // a, b', c*
    NSArray *const sourceSections = @[ CollectionSection(@"a", nil, nil), CollectionSection(@"b", nil, nil), CollectionSection(@"c", nil, nil) ];
    NSArray *const destinationSections = @[ CollectionSection(@"a", nil, nil), CollectionSection(@"b", @"B'", nil), CollectionSection(@"c", nil, @[@"New item!"]) ];

    MUKDataSourceCollectionUpdate *const update = [[MUKDataSourceCollectionUpdate alloc] initWithSourceSections:sourceSections destinationSections:destinationSections];

    NSIndexSet *const reloadedSections = [NSIndexSet indexSetWithIndex:1];
    XCTAssertEqualObjects(update.reloadedSectionIndexes, reloadedSections);
}

- (void)testItemInsertion {
    // a, b
    // a, c, b
    NSArray *const sourceItems = @[ @"a", @"b" ];
    NSArray *const destinationItems = @[ @"a", @"c", @"b" ];

    NSArray *const sourceSections = @[CollectionSection(@"a", nil, sourceItems)];
    NSArray *const destinationSections = @[CollectionSection(@"a", nil, destinationItems)];

    MUKDataSource *const dataSource = [[BasicCollectionDataSource alloc] init];
    UICollectionView *const collectionView = [self newCollectionViewWithSections:sourceSections dataSource:dataSource];
    XCTAssertEqual([collectionView numberOfSections], sourceSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:0], sourceItems.count);

    MUKDataSourceCollectionUpdate *const update = [dataSource setCollectionSections:destinationSections];

    XCTestExpectation *expectation = [self expectationWithDescription:@"Update finished"];
    XCTAssertNoThrow([update applyToCollectionView:collectionView completion:^(BOOL finished)
    {
        [expectation fulfill];
    }]);
    [self waitForExpectationsWithTimeout:kUpdateTimeout handler:nil];

    XCTAssertFalse(update.needsReloadData);
    XCTAssertEqual([collectionView numberOfSections], destinationSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:0], destinationItems.count);
}

- (void)testItemInsertionWithSectionReload {
    // a, b
    // a, c, b
    NSArray *const sourceItems = @[ @"a", @"b" ];
    NSArray *const destinationItems = @[ @"a", @"c", @"b" ];

    NSArray *const sourceSections = @[CollectionSection(@"a", nil, sourceItems)];
    NSArray *const destinationSections = @[CollectionSection(@"a", @"A'", destinationItems)];

    MUKDataSource *const dataSource = [[BasicCollectionDataSource alloc] init];
    UICollectionView *const collectionView = [self newCollectionViewWithSections:sourceSections dataSource:dataSource];
    XCTAssertEqual([collectionView numberOfSections], sourceSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:0], sourceItems.count);

    MUKDataSourceCollectionUpdate *const update = [dataSource setCollectionSections:destinationSections];

    NSIndexSet *const reloadedSections = [NSIndexSet indexSetWithIndex:0];
    NSSet *const insertedItemIndexPaths = [NSSet setWithObjects:IndexPathWithItem(1), nil];

    XCTAssertEqual(update.insertedSectionIndexes.count, 0);
    XCTAssertEqual(update.deletedSectionIndexes.count, 0);
    XCTAssertEqualObjects(update.reloadedSectionIndexes, reloadedSections);
    XCTAssertEqual(update.sectionMovements.count, 0);

    XCTAssertEqualObjects(update.insertedItemIndexPaths, insertedItemIndexPaths);
    XCTAssertEqual(update.deletedItemIndexPaths.count, 0);
    XCTAssertEqual(update.reloadedItemIndexPaths.count, 0);
    XCTAssertEqual(update.itemMovements.count, 0);

    XCTestExpectation *expectation = [self expectationWithDescription:@"Update finished"];
    XCTAssertNoThrow([update applyToCollectionView:collectionView completion:^(BOOL finished)
    {
        [expectation fulfill];
    }]);
    [self waitForExpectationsWithTimeout:kUpdateTimeout handler:nil];

    XCTAssertFalse(update.needsReloadData);
    XCTAssertEqual([collectionView numberOfSections], destinationSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:0], destinationItems.count);
}

- (void)testItemInsertionWithSectionMovement {
    // a, b
    // a, c, b
    NSArray *const sourceItems = @[ @"a", @"b" ];
    NSArray *const destinationItems = @[ @"a", @"c", @"b" ];

    NSArray *const sourceSections = @[ CollectionSection(@"a", nil, sourceItems), CollectionSection(@"b", nil, nil)];
    NSArray *const destinationSections = @[ CollectionSection(@"b", nil, nil), CollectionSection(@"a", nil, destinationItems) ];

    MUKDataSource *const dataSource = [[BasicCollectionDataSource alloc] init];
    UICollectionView *const collectionView = [self newCollectionViewWithSections:sourceSections dataSource:dataSource];
    XCTAssertEqual([collectionView numberOfSections], sourceSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:0], sourceItems.count);

    MUKDataSourceCollectionUpdate *const update = [dataSource setCollectionSections:destinationSections];

    XCTestExpectation *expectation = [self expectationWithDescription:@"Update finished"];
    XCTAssertNoThrow([update applyToCollectionView:collectionView completion:^(BOOL finished)
    {
        [expectation fulfill];
    }]);
    [self waitForExpectationsWithTimeout:kUpdateTimeout handler:nil];

    XCTAssert(update.needsReloadData);
    XCTAssertEqual([collectionView numberOfSections], destinationSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:1], destinationItems.count);
}

- (void)testItemDeletion {
    // a, b, c
    // a, c
    NSArray *const sourceItems = @[ @"a", @"b", @"c" ];
    NSArray *const destinationItems = @[ @"a", @"c" ];

    NSArray *const sourceSections = @[CollectionSection(@"a", nil, sourceItems)];
    NSArray *const destinationSections = @[CollectionSection(@"a", nil, destinationItems)];

    MUKDataSource *const dataSource = [[BasicCollectionDataSource alloc] init];
    UICollectionView *const collectionView = [self newCollectionViewWithSections:sourceSections dataSource:dataSource];
    XCTAssertEqual([collectionView numberOfSections], sourceSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:0], sourceItems.count);

    MUKDataSourceCollectionUpdate *const update = [dataSource setCollectionSections:destinationSections];

    XCTestExpectation *expectation = [self expectationWithDescription:@"Update finished"];
    XCTAssertNoThrow([update applyToCollectionView:collectionView completion:^(BOOL finished)
    {
        [expectation fulfill];
    }]);
    [self waitForExpectationsWithTimeout:kUpdateTimeout handler:nil];

    XCTAssertFalse(update.needsReloadData);
    XCTAssertEqual([collectionView numberOfSections], destinationSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:0], destinationItems.count);
}

- (void)testItemDeletionWithSectionReload {
    // a, b, c
    // a, c
    NSArray *const sourceItems = @[ @"a", @"b", @"c" ];
    NSArray *const destinationItems = @[ @"a", @"c" ];

    NSArray *const sourceSections = @[CollectionSection(@"a", nil, sourceItems)];
    NSArray *const destinationSections = @[CollectionSection(@"a", @"A'", destinationItems)];

    MUKDataSource *const dataSource = [[BasicCollectionDataSource alloc] init];
    UICollectionView *const collectionView = [self newCollectionViewWithSections:sourceSections dataSource:dataSource];
    XCTAssertEqual([collectionView numberOfSections], sourceSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:0], sourceItems.count);

    MUKDataSourceCollectionUpdate *const update = [dataSource setCollectionSections:destinationSections];

    NSIndexSet *const reloadedSections = [NSIndexSet indexSetWithIndex:0];
    NSSet *const deletedItemIndexPaths = [NSSet setWithObjects:IndexPathWithItem(1), nil];

    XCTAssertEqual(update.insertedSectionIndexes.count, 0);
    XCTAssertEqual(update.deletedSectionIndexes.count, 0);
    XCTAssertEqualObjects(update.reloadedSectionIndexes, reloadedSections);
    XCTAssertEqual(update.sectionMovements.count, 0);

    XCTAssertEqual(update.insertedItemIndexPaths.count, 0);
    XCTAssertEqualObjects(update.deletedItemIndexPaths, deletedItemIndexPaths);
    XCTAssertEqual(update.reloadedItemIndexPaths.count, 0);
    XCTAssertEqual(update.itemMovements.count, 0);

    XCTestExpectation *expectation = [self expectationWithDescription:@"Update finished"];
    XCTAssertNoThrow([update applyToCollectionView:collectionView completion:^(BOOL finished)
    {
        [expectation fulfill];
    }]);
    [self waitForExpectationsWithTimeout:kUpdateTimeout handler:nil];

    XCTAssertFalse(update.needsReloadData);
    XCTAssertEqual([collectionView numberOfSections], destinationSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:0], destinationItems.count);
}

- (void)testItemDeletionWithSectionMovement {
    // a, b, c
    // a, c
    NSArray *const sourceItems = @[ @"a", @"b", @"c" ];
    NSArray *const destinationItems = @[ @"a", @"c" ];

    NSArray *const sourceSections = @[ CollectionSection(@"a", nil, sourceItems), CollectionSection(@"b", nil, nil)];
    NSArray *const destinationSections = @[ CollectionSection(@"b", nil, nil), CollectionSection(@"a", nil, destinationItems) ];

    MUKDataSource *const dataSource = [[BasicCollectionDataSource alloc] init];
    UICollectionView *const collectionView = [self newCollectionViewWithSections:sourceSections dataSource:dataSource];
    XCTAssertEqual([collectionView numberOfSections], sourceSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:0], sourceItems.count);

    MUKDataSourceCollectionUpdate *const update = [dataSource setCollectionSections:destinationSections];

    XCTestExpectation *expectation = [self expectationWithDescription:@"Update finished"];
    XCTAssertNoThrow([update applyToCollectionView:collectionView completion:^(BOOL finished)
    {
        [expectation fulfill];
    }]);
    [self waitForExpectationsWithTimeout:kUpdateTimeout handler:nil];

    XCTAssert(update.needsReloadData);
    XCTAssertEqual([collectionView numberOfSections], destinationSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:1], destinationItems.count);
}

- (void)testItemReload {
    // a, b, c
    // a, b', c
    NSArray *const sourceItems = @[ @"a", [[BasicItem alloc] initWithIdentifier:@"b" title:@"B"], @"c" ];
    NSArray *const destinationItems = @[ @"a", [[BasicItem alloc] initWithIdentifier:@"b" title:@"B'"], @"c" ];

    NSArray *const sourceSections = @[CollectionSection(@"a", nil, sourceItems)];
    NSArray *const destinationSections = @[CollectionSection(@"a", nil, destinationItems)];

    MUKDataSource *const dataSource = [[BasicCollectionDataSource alloc] init];
    UICollectionView *const collectionView = [self newCollectionViewWithSections:sourceSections dataSource:dataSource];
    XCTAssertEqual([collectionView numberOfSections], sourceSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:0], sourceItems.count);

    MUKDataSourceCollectionUpdate *const update = [dataSource setCollectionSections:destinationSections];

    NSSet *const reloadedItemIndexPaths = [NSSet setWithObjects:IndexPathWithItem(1), nil];

    XCTAssertEqual(update.insertedSectionIndexes.count, 0);
    XCTAssertEqual(update.deletedSectionIndexes.count, 0);
    XCTAssertEqual(update.reloadedSectionIndexes.count, 0);
    XCTAssertEqual(update.sectionMovements.count, 0);

    XCTAssertEqual(update.insertedItemIndexPaths.count, 0);
    XCTAssertEqual(update.deletedItemIndexPaths.count, 0);
    XCTAssertEqualObjects(update.reloadedItemIndexPaths, reloadedItemIndexPaths);
    XCTAssertEqual(update.itemMovements.count, 0);

    XCTestExpectation *expectation = [self expectationWithDescription:@"Update finished"];
    XCTAssertNoThrow([update applyToCollectionView:collectionView completion:^(BOOL finished)
    {
        [expectation fulfill];
    }]);
    [self waitForExpectationsWithTimeout:kUpdateTimeout handler:nil];

    XCTAssertFalse(update.needsReloadData);
    XCTAssertEqual([collectionView numberOfSections], destinationSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:0], destinationItems.count);
}

- (void)testItemReloadWithSectionReload {
    // a, b, c
    // a, b', c
    NSArray *const sourceItems = @[ @"a", [[BasicItem alloc] initWithIdentifier:@"b" title:@"B"], @"c" ];
    NSArray *const destinationItems = @[ @"a", [[BasicItem alloc] initWithIdentifier:@"b" title:@"B'"], @"c" ];

    NSArray *const sourceSections = @[CollectionSection(@"a", nil, sourceItems)];
    NSArray *const destinationSections = @[CollectionSection(@"a", @"A'", destinationItems)];

    MUKDataSource *const dataSource = [[BasicCollectionDataSource alloc] init];
    UICollectionView *const collectionView = [self newCollectionViewWithSections:sourceSections dataSource:dataSource];
    XCTAssertEqual([collectionView numberOfSections], sourceSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:0], sourceItems.count);

    MUKDataSourceCollectionUpdate *const update = [dataSource setCollectionSections:destinationSections];

    NSIndexSet *const reloadedSections = [NSIndexSet indexSetWithIndex:0];
    NSSet *const reloadedItemIndexPaths = [NSSet setWithObjects:IndexPathWithItem(1), nil];

    XCTAssertEqual(update.insertedSectionIndexes.count, 0);
    XCTAssertEqual(update.deletedSectionIndexes.count, 0);
    XCTAssertEqualObjects(update.reloadedSectionIndexes, reloadedSections);
    XCTAssertEqual(update.sectionMovements.count, 0);

    XCTAssertEqual(update.insertedItemIndexPaths.count, 0);
    XCTAssertEqual(update.deletedItemIndexPaths.count, 0);
    XCTAssertEqualObjects(update.reloadedItemIndexPaths, reloadedItemIndexPaths);
    XCTAssertEqual(update.itemMovements.count, 0);

    XCTestExpectation *expectation = [self expectationWithDescription:@"Update finished"];
    XCTAssertNoThrow([update applyToCollectionView:collectionView completion:^(BOOL finished)
    {
        [expectation fulfill];
    }]);
    [self waitForExpectationsWithTimeout:kUpdateTimeout handler:nil];

    XCTAssertFalse(update.needsReloadData);
    XCTAssertEqual([collectionView numberOfSections], destinationSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:0], destinationItems.count);
}

- (void)testItemReloadWithSectionMovement {
    // a, b, c
    // a, b', c
    NSArray *const sourceItems = @[ @"a", [[BasicItem alloc] initWithIdentifier:@"b" title:@"B"], @"c" ];
    NSArray *const destinationItems = @[ @"a", [[BasicItem alloc] initWithIdentifier:@"b" title:@"B'"], @"c" ];

    NSArray *const sourceSections = @[ CollectionSection(@"a", nil, sourceItems), CollectionSection(@"b", nil, nil)];
    NSArray *const destinationSections = @[ CollectionSection(@"b", nil, nil), CollectionSection(@"a", nil, destinationItems) ];

    MUKDataSource *const dataSource = [[BasicCollectionDataSource alloc] init];
    UICollectionView *const collectionView = [self newCollectionViewWithSections:sourceSections dataSource:dataSource];
    XCTAssertEqual([collectionView numberOfSections], sourceSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:0], sourceItems.count);

    MUKDataSourceCollectionUpdate *const update = [dataSource setCollectionSections:destinationSections];

    XCTestExpectation *expectation = [self expectationWithDescription:@"Update finished"];
    XCTAssertNoThrow([update applyToCollectionView:collectionView completion:^(BOOL finished)
    {
        [expectation fulfill];
    }]);
    [self waitForExpectationsWithTimeout:kUpdateTimeout handler:nil];

    XCTAssertFalse(update.needsReloadData);
    XCTAssertEqual([collectionView numberOfSections], destinationSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:1], destinationItems.count);
}

- (void)testItemMovements {
    // a, b, c, d
    // c, a, d, b
    NSArray *const sourceItems = @[ @"a", @"b", @"c", @"d" ];
    NSArray *const destinationItems = @[ @"c", @"a", @"d", @"b" ];

    NSArray *const sourceSections = @[CollectionSection(@"a", nil, sourceItems)];
    NSArray *const destinationSections = @[CollectionSection(@"a", nil, destinationItems)];

    MUKDataSource *const dataSource = [[BasicCollectionDataSource alloc] init];
    UICollectionView *const collectionView = [self newCollectionViewWithSections:sourceSections dataSource:dataSource];
    XCTAssertEqual([collectionView numberOfSections], sourceSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:0], sourceItems.count);

    MUKDataSourceCollectionUpdate *const update = [dataSource setCollectionSections:destinationSections];

    XCTestExpectation *expectation = [self expectationWithDescription:@"Update finished"];
    XCTAssertNoThrow([update applyToCollectionView:collectionView completion:^(BOOL finished)
    {
        [expectation fulfill];
    }]);
    [self waitForExpectationsWithTimeout:kUpdateTimeout handler:nil];

    XCTAssertFalse(update.needsReloadData);
    XCTAssertEqual([collectionView numberOfSections], destinationSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:0], destinationItems.count);
}

- (void)testItemMovementBetweenSections {
    NSArray *const sourceItemsA = @[ @"a", @"b" ];
    NSArray *const sourceItemsB = @[ @"c", @"d", @"e", @"f" ];

    NSArray *const destinationItemsA = @[ @"c", @"b", @"f", @"d" ];
    NSArray *const destinationItemsB = @[ @"a", @"e" ];

    NSArray *const sourceSections = @[ CollectionSection(@"a", nil, sourceItemsA), CollectionSection(@"b", nil, sourceItemsB) ];
    NSArray *const destinationSections = @[ CollectionSection(@"a", nil, destinationItemsA), CollectionSection(@"b", nil, destinationItemsB) ];

    MUKDataSource *const dataSource = [[BasicCollectionDataSource alloc] init];
    UICollectionView *const collectionView = [self newCollectionViewWithSections:sourceSections dataSource:dataSource];
    XCTAssertEqual([collectionView numberOfSections], sourceSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:0], sourceItemsA.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:1], sourceItemsB.count);

    MUKDataSourceCollectionUpdate *const update = [dataSource setCollectionSections:destinationSections];

    XCTestExpectation *expectation = [self expectationWithDescription:@"Update finished"];
    XCTAssertNoThrow([update applyToCollectionView:collectionView completion:^(BOOL finished)
    {
        [expectation fulfill];
    }]);
    [self waitForExpectationsWithTimeout:kUpdateTimeout handler:nil];

    XCTAssertFalse(update.needsReloadData);
    XCTAssertEqual([collectionView numberOfSections], destinationSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:0], destinationItemsA.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:1], destinationItemsB.count);
}

- (void)testItemMovementsWithSectionReload {
    // a, b, c, d
    // c, a, d, b
    NSArray *const sourceItems = @[ @"a", @"b", @"c", @"d" ];
    NSArray *const destinationItems = @[ @"c", @"a", @"d", @"b" ];

    NSArray *const sourceSections = @[CollectionSection(@"a", @"A", sourceItems)];
    NSArray *const destinationSections = @[CollectionSection(@"a", @"A'", destinationItems)];

    MUKDataSource *const dataSource = [[BasicCollectionDataSource alloc] init];
    UICollectionView *const collectionView = [self newCollectionViewWithSections:sourceSections dataSource:dataSource];
    XCTAssertEqual([collectionView numberOfSections], sourceSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:0], sourceItems.count);

    MUKDataSourceCollectionUpdate *const update = [dataSource setCollectionSections:destinationSections];

    // 0->1, 1->3
    NSSet *const itemMovements = [NSSet setWithObjects:[[MUKDataSourceContentSectionItemMovement alloc] initWithSourceIndexPath:IndexPathWithItem(0) destinationIndexPath:IndexPathWithItem(1)], [[MUKDataSourceContentSectionItemMovement alloc] initWithSourceIndexPath:IndexPathWithItem(1) destinationIndexPath:IndexPathWithItem(3)], nil];
    NSIndexSet *const reloadedSections = [NSIndexSet indexSetWithIndex:0];

    XCTAssertEqual(update.insertedSectionIndexes.count, 0);
    XCTAssertEqual(update.deletedSectionIndexes.count, 0);
    XCTAssertEqualObjects(update.reloadedSectionIndexes, reloadedSections);
    XCTAssertEqual(update.sectionMovements.count, 0);

    XCTAssertEqual(update.insertedItemIndexPaths.count, 0);
    XCTAssertEqual(update.deletedItemIndexPaths.count, 0);
    XCTAssertEqual(update.reloadedItemIndexPaths.count, 0);
    XCTAssertEqualObjects(update.itemMovements, itemMovements);

    XCTestExpectation *expectation = [self expectationWithDescription:@"Update finished"];
    XCTAssertNoThrow([update applyToCollectionView:collectionView completion:^(BOOL finished)
    {
        [expectation fulfill];
    }]);
    [self waitForExpectationsWithTimeout:kUpdateTimeout handler:nil];

    XCTAssertFalse(update.needsReloadData);
    XCTAssertEqual([collectionView numberOfSections], destinationSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:0], destinationItems.count);
}

- (void)testItemMovementWithSectionMovement {
    NSArray *const sourceItemsA = @[ @"a", @"b" ];
    NSArray *const sourceItemsB = @[ @"c", @"d" ];
    NSArray *const sourceItemsC = @[ @"e", @"f" ];

    NSArray *const destinationItemsA = @[ @"b" ];
    NSArray *const destinationItemsC = @[ @"e", @"f" ];
    NSArray *const destinationItemsB = @[ @"c", @"a", @"d" ];

    NSArray *const sourceSections = @[ CollectionSection(@"a", nil, sourceItemsA), CollectionSection(@"b", nil, sourceItemsB), CollectionSection(@"c", nil, sourceItemsC) ];
    NSArray *const destinationSections = @[ CollectionSection(@"a", nil, destinationItemsA), CollectionSection(@"c", nil, destinationItemsC), CollectionSection(@"b", nil, destinationItemsB) ];

    MUKDataSource *const dataSource = [[BasicCollectionDataSource alloc] init];
    UICollectionView *const collectionView = [self newCollectionViewWithSections:sourceSections dataSource:dataSource];
    XCTAssertEqual([collectionView numberOfSections], sourceSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:0], sourceItemsA.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:1], sourceItemsB.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:2], sourceItemsC.count);

    MUKDataSourceCollectionUpdate *const update = [dataSource setCollectionSections:destinationSections];

    XCTestExpectation *expectation = [self expectationWithDescription:@"Update finished"];
    XCTAssertNoThrow([update applyToCollectionView:collectionView completion:^(BOOL finished)
    {
        [expectation fulfill];
    }]);
    [self waitForExpectationsWithTimeout:kUpdateTimeout handler:nil];

    XCTAssert(update.needsReloadData);
    XCTAssertEqual([collectionView numberOfSections], destinationSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:0], destinationItemsA.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:1], destinationItemsC.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:2], destinationItemsB.count);
}

- (void)testItemMovementToInsertedSection {
    NSArray *const sourceItemsA = @[ @"a", @"b" ];

    NSArray *const destinationItemsA = @[ @"b" ];
    NSArray *const destinationItemsB = @[ @"c", @"a" ];

    NSArray *const sourceSections = @[ CollectionSection(@"a", nil, sourceItemsA) ];
    NSArray *const destinationSections = @[ CollectionSection(@"a", nil, destinationItemsA), CollectionSection(@"b", nil, destinationItemsB) ];

    MUKDataSource *const dataSource = [[BasicCollectionDataSource alloc] init];
    UICollectionView *const collectionView = [self newCollectionViewWithSections:sourceSections dataSource:dataSource];
    XCTAssertEqual([collectionView numberOfSections], sourceSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:0], sourceItemsA.count);

    MUKDataSourceCollectionUpdate *const update = [dataSource setCollectionSections:destinationSections];

    XCTestExpectation *expectation = [self expectationWithDescription:@"Update finished"];
    XCTAssertNoThrow([update applyToCollectionView:collectionView completion:^(BOOL finished)
    {
        [expectation fulfill];
    }]);
    [self waitForExpectationsWithTimeout:kUpdateTimeout handler:nil];

    XCTAssertFalse(update.needsReloadData);
    XCTAssertEqual([collectionView numberOfSections], destinationSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:0], destinationItemsA.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:1], destinationItemsB.count);
}

- (void)testItemMovementFromDeletedSection {
    NSArray *const sourceItemsA = @[ @"a", @"b" ];
    NSArray *const sourceItemsB = @[ @"c" ];

    NSArray *const destinationItemsB = @[ @"c", @"a" ];

    NSArray *const sourceSections = @[ CollectionSection(@"a", nil, sourceItemsA), CollectionSection(@"b", nil, sourceItemsB) ];
    NSArray *const destinationSections = @[ CollectionSection(@"b", nil, destinationItemsB) ];

    MUKDataSource *const dataSource = [[BasicCollectionDataSource alloc] init];
    UICollectionView *const collectionView = [self newCollectionViewWithSections:sourceSections dataSource:dataSource];
    XCTAssertEqual([collectionView numberOfSections], sourceSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:0], sourceItemsA.count);

    MUKDataSourceCollectionUpdate *const update = [dataSource setCollectionSections:destinationSections];

    XCTestExpectation *expectation = [self expectationWithDescription:@"Update finished"];
    XCTAssertNoThrow([update applyToCollectionView:collectionView completion:^(BOOL finished)
    {
        [expectation fulfill];
    }]);
    [self waitForExpectationsWithTimeout:kUpdateTimeout handler:nil];

    XCTAssertFalse(update.needsReloadData);
    XCTAssertEqual([collectionView numberOfSections], destinationSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:0], destinationItemsB.count);
}

- (void)testItemComboInsertionDeletion {
    // a, b
    // c, b
    NSArray *const sourceItems = @[ @"a", @"b" ];
    NSArray *const destinationItems = @[ @"c", @"b" ];

    NSArray *const sourceSections = @[CollectionSection(@"a", nil, sourceItems)];
    NSArray *const destinationSections = @[CollectionSection(@"a", nil, destinationItems)];

    MUKDataSource *const dataSource = [[BasicCollectionDataSource alloc] init];
    UICollectionView *const collectionView = [self newCollectionViewWithSections:sourceSections dataSource:dataSource];
    XCTAssertEqual([collectionView numberOfSections], sourceSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:0], sourceItems.count);

    MUKDataSourceCollectionUpdate *const update = [dataSource setCollectionSections:destinationSections];

    XCTestExpectation *expectation = [self expectationWithDescription:@"Update finished"];
    XCTAssertNoThrow([update applyToCollectionView:collectionView completion:^(BOOL finished)
    {
        [expectation fulfill];
    }]);
    [self waitForExpectationsWithTimeout:kUpdateTimeout handler:nil];

    XCTAssertFalse(update.needsReloadData);
    XCTAssertEqual([collectionView numberOfSections], destinationSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:0], destinationItems.count);
}

- (void)testItemComboInsertionReload {
    // a, b
    // c, d, a, e, b', f
    NSArray *const sourceItems = @[ @"a", [[BasicItem alloc] initWithIdentifier:@"b" title:@"b"] ];
    NSArray *const destinationItems = @[ @"c", @"d", @"a", @"e", [[BasicItem alloc] initWithIdentifier:@"b" title:@"b'"], @"f" ];

    NSArray *const sourceSections = @[CollectionSection(@"a", nil, sourceItems)];
    NSArray *const destinationSections = @[CollectionSection(@"a", nil, destinationItems)];

    MUKDataSource *const dataSource = [[BasicCollectionDataSource alloc] init];
    UICollectionView *const collectionView = [self newCollectionViewWithSections:sourceSections dataSource:dataSource];
    XCTAssertEqual([collectionView numberOfSections], sourceSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:0], sourceItems.count);

    MUKDataSourceCollectionUpdate *const update = [dataSource setCollectionSections:destinationSections];

    NSSet *const insertedItemIndexPaths = [NSSet setWithObjects:IndexPathWithItem(0), IndexPathWithItem(1), IndexPathWithItem(3), IndexPathWithItem(5), nil];
    NSSet *const reloadedItemIndexPaths = [NSSet setWithObjects:IndexPathWithItem(4), nil];

    XCTAssertEqual(update.insertedSectionIndexes.count, 0);
    XCTAssertEqual(update.deletedSectionIndexes.count, 0);
    XCTAssertEqual(update.reloadedSectionIndexes.count, 0);
    XCTAssertEqual(update.sectionMovements.count, 0);

    XCTAssertEqualObjects(update.insertedItemIndexPaths, insertedItemIndexPaths);
    XCTAssertEqual(update.deletedItemIndexPaths.count, 0);
    XCTAssertEqualObjects(update.reloadedItemIndexPaths, reloadedItemIndexPaths);
    XCTAssertEqual(update.itemMovements.count, 0);

    XCTestExpectation *expectation = [self expectationWithDescription:@"Update finished"];
    XCTAssertNoThrow([update applyToCollectionView:collectionView completion:^(BOOL finished)
    {
        [expectation fulfill];
    }]);
    [self waitForExpectationsWithTimeout:kUpdateTimeout handler:nil];

    XCTAssertFalse(update.needsReloadData);
    XCTAssertEqual([collectionView numberOfSections], destinationSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:0], destinationItems.count);
}

- (void)testItemComboInsertionMovements {
    // a, b, c, d
    // e, c, b, d, f, a
    NSArray *const sourceItems = @[ @"a", @"b", @"c", @"d" ];
    NSArray *const destinationItems = @[ @"e", @"c", @"b", @"d", @"f", @"a" ];

    NSArray *const sourceSections = @[CollectionSection(@"a", nil, sourceItems)];
    NSArray *const destinationSections = @[CollectionSection(@"a", nil, destinationItems)];

    MUKDataSource *const dataSource = [[BasicCollectionDataSource alloc] init];
    UICollectionView *const collectionView = [self newCollectionViewWithSections:sourceSections dataSource:dataSource];
    XCTAssertEqual([collectionView numberOfSections], sourceSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:0], sourceItems.count);

    MUKDataSourceCollectionUpdate *const update = [dataSource setCollectionSections:destinationSections];

    XCTestExpectation *expectation = [self expectationWithDescription:@"Update finished"];
    XCTAssertNoThrow([update applyToCollectionView:collectionView completion:^(BOOL finished)
    {
        [expectation fulfill];
    }]);
    [self waitForExpectationsWithTimeout:kUpdateTimeout handler:nil];

    XCTAssertFalse(update.needsReloadData);
    XCTAssertEqual([collectionView numberOfSections], destinationSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:0], destinationItems.count);
}

- (void)testItemComboDeletionReload {
    // a, b, c, d
    // b, d'
    NSArray *const sourceItems = @[ @"a", @"b", @"c", [[BasicItem alloc] initWithIdentifier:@"d" title:@"d"] ];
    NSArray *const destinationItems = @[ @"b", [[BasicItem alloc] initWithIdentifier:@"d" title:@"d'"] ];

    NSArray *const sourceSections = @[CollectionSection(@"a", nil, sourceItems)];
    NSArray *const destinationSections = @[CollectionSection(@"a", nil, destinationItems)];

    MUKDataSource *const dataSource = [[BasicCollectionDataSource alloc] init];
    UICollectionView *const collectionView = [self newCollectionViewWithSections:sourceSections dataSource:dataSource];
    XCTAssertEqual([collectionView numberOfSections], sourceSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:0], sourceItems.count);

    MUKDataSourceCollectionUpdate *const update = [dataSource setCollectionSections:destinationSections];

    NSSet *const deletedItemIndexPaths = [NSSet setWithObjects:IndexPathWithItem(0), IndexPathWithItem(2), nil];
    NSSet *const reloadedItemIndexPaths = [NSSet setWithObjects:IndexPathWithItem(1), nil];

    XCTAssertEqual(update.insertedSectionIndexes.count, 0);
    XCTAssertEqual(update.deletedSectionIndexes.count, 0);
    XCTAssertEqual(update.reloadedSectionIndexes.count, 0);
    XCTAssertEqual(update.sectionMovements.count, 0);

    XCTAssertEqual(update.insertedItemIndexPaths.count, 0);
    XCTAssertEqualObjects(update.deletedItemIndexPaths, deletedItemIndexPaths);
    XCTAssertEqualObjects(update.reloadedItemIndexPaths, reloadedItemIndexPaths);
    XCTAssertEqual(update.itemMovements.count, 0);

    XCTestExpectation *expectation = [self expectationWithDescription:@"Update finished"];
    XCTAssertNoThrow([update applyToCollectionView:collectionView completion:^(BOOL finished)
    {
        [expectation fulfill];
    }]);
    [self waitForExpectationsWithTimeout:kUpdateTimeout handler:nil];

    XCTAssertFalse(update.needsReloadData);
    XCTAssertEqual([collectionView numberOfSections], destinationSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:0], destinationItems.count);
}

- (void)testItemComboDeletionMovements {
    // a, b, c, d, e
    // b, e, c
    NSArray *const sourceItems = @[ @"a", @"b", @"c", @"d", @"e" ];
    NSArray *const destinationItems = @[ @"b", @"e", @"c" ];

    NSArray *const sourceSections = @[CollectionSection(@"a", nil, sourceItems)];
    NSArray *const destinationSections = @[CollectionSection(@"a", nil, destinationItems)];

    MUKDataSource *const dataSource = [[BasicCollectionDataSource alloc] init];
    UICollectionView *const collectionView = [self newCollectionViewWithSections:sourceSections dataSource:dataSource];
    XCTAssertEqual([collectionView numberOfSections], sourceSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:0], sourceItems.count);

    MUKDataSourceCollectionUpdate *const update = [dataSource setCollectionSections:destinationSections];

    XCTestExpectation *expectation = [self expectationWithDescription:@"Update finished"];
    XCTAssertNoThrow([update applyToCollectionView:collectionView completion:^(BOOL finished)
    {
        [expectation fulfill];
    }]);
    [self waitForExpectationsWithTimeout:kUpdateTimeout handler:nil];

    XCTAssertFalse(update.needsReloadData);
    XCTAssertEqual([collectionView numberOfSections], destinationSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:0], destinationItems.count);
}

- (void)testItemComboReloadMovements {
    // a, b, c
    // c, b, a'
    NSArray *const sourceItems = @[ [[BasicItem alloc] initWithIdentifier:@"a" title:@"a"], @"b", @"c" ];
    NSArray *const destinationItems = @[ @"c", @"b", [[BasicItem alloc] initWithIdentifier:@"a" title:@"a'"] ];

    NSArray *const sourceSections = @[CollectionSection(@"a", nil, sourceItems)];
    NSArray *const destinationSections = @[CollectionSection(@"a", nil, destinationItems)];

    MUKDataSource *const dataSource = [[BasicCollectionDataSource alloc] init];
    UICollectionView *const collectionView = [self newCollectionViewWithSections:sourceSections dataSource:dataSource];
    XCTAssertEqual([collectionView numberOfSections], sourceSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:0], sourceItems.count);

    MUKDataSourceCollectionUpdate *const update = [dataSource setCollectionSections:destinationSections];

    NSSet *const reloadedItemIndexPaths = [NSSet setWithObjects:IndexPathWithItem(2), nil];
    NSSet *const itemMovements = [NSSet setWithObjects:[[MUKDataSourceContentSectionItemMovement alloc] initWithSourceIndexPath:IndexPathWithItem(0) destinationIndexPath:IndexPathWithItem(2)], [[MUKDataSourceContentSectionItemMovement alloc] initWithSourceIndexPath:IndexPathWithItem(2) destinationIndexPath:IndexPathWithItem(0)], nil];

    XCTAssertEqual(update.insertedSectionIndexes.count, 0);
    XCTAssertEqual(update.deletedSectionIndexes.count, 0);
    XCTAssertEqual(update.reloadedSectionIndexes.count, 0);
    XCTAssertEqual(update.sectionMovements.count, 0);

    XCTAssertEqual(update.insertedItemIndexPaths.count, 0);
    XCTAssertEqual(update.deletedItemIndexPaths.count, 0);
    XCTAssertEqualObjects(update.reloadedItemIndexPaths, reloadedItemIndexPaths);
    XCTAssertEqualObjects(update.itemMovements, itemMovements);

    XCTestExpectation *expectation = [self expectationWithDescription:@"Update finished"];
    XCTAssertNoThrow([update applyToCollectionView:collectionView completion:^(BOOL finished)
    {
        [expectation fulfill];
    }]);
    [self waitForExpectationsWithTimeout:kUpdateTimeout handler:nil];

    XCTAssertFalse(update.needsReloadData);
    XCTAssertEqual([collectionView numberOfSections], destinationSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:0], destinationItems.count);
}

- (void)testItemComboInsertionDeletionReload {
    // a, b, c
    // a, d, b', e
    NSArray *const sourceItems = @[ @"a", [[BasicItem alloc] initWithIdentifier:@"b" title:@"b"], @"c" ];
    NSArray *const destinationItems = @[ @"a", @"d", [[BasicItem alloc] initWithIdentifier:@"b" title:@"b'"], @"e" ];

    NSArray *const sourceSections = @[CollectionSection(@"a", nil, sourceItems)];
    NSArray *const destinationSections = @[CollectionSection(@"a", nil, destinationItems)];

    MUKDataSource *const dataSource = [[BasicCollectionDataSource alloc] init];
    UICollectionView *const collectionView = [self newCollectionViewWithSections:sourceSections dataSource:dataSource];
    XCTAssertEqual([collectionView numberOfSections], sourceSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:0], sourceItems.count);

    MUKDataSourceCollectionUpdate *const update = [dataSource setCollectionSections:destinationSections];

    NSSet *const insertedItemIndexPaths = [NSSet setWithObjects:IndexPathWithItem(1), IndexPathWithItem(3), nil];
    NSSet *const deletedItemIndexPaths = [NSSet setWithObjects:IndexPathWithItem(2), nil];
    NSSet *const reloadedItemIndexPaths = [NSSet setWithObjects:IndexPathWithItem(2), nil];

    XCTAssertEqual(update.insertedSectionIndexes.count, 0);
    XCTAssertEqual(update.deletedSectionIndexes.count, 0);
    XCTAssertEqual(update.reloadedSectionIndexes.count, 0);
    XCTAssertEqual(update.sectionMovements.count, 0);

    XCTAssertEqualObjects(update.insertedItemIndexPaths, insertedItemIndexPaths);
    XCTAssertEqualObjects(update.deletedItemIndexPaths, deletedItemIndexPaths);
    XCTAssertEqualObjects(update.reloadedItemIndexPaths, reloadedItemIndexPaths);
    XCTAssertEqual(update.itemMovements.count, 0);

    XCTestExpectation *expectation = [self expectationWithDescription:@"Update finished"];
    XCTAssertNoThrow([update applyToCollectionView:collectionView completion:^(BOOL finished)
    {
        [expectation fulfill];
    }]);
    [self waitForExpectationsWithTimeout:kUpdateTimeout handler:nil];

    XCTAssertFalse(update.needsReloadData);
    XCTAssertEqual([collectionView numberOfSections], destinationSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:0], destinationItems.count);
}

- (void)testItemComboInsertionDeletionMovement {
    // a, b, c
    // b, d, a, e
    NSArray *const sourceItems = @[ @"a", @"b", @"c" ];
    NSArray *const destinationItems = @[ @"b", @"d", @"a", @"e" ];

    NSArray *const sourceSections = @[CollectionSection(@"a", nil, sourceItems)];
    NSArray *const destinationSections = @[CollectionSection(@"a", nil, destinationItems)];

    MUKDataSource *const dataSource = [[BasicCollectionDataSource alloc] init];
    UICollectionView *const collectionView = [self newCollectionViewWithSections:sourceSections dataSource:dataSource];
    XCTAssertEqual([collectionView numberOfSections], sourceSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:0], sourceItems.count);

    MUKDataSourceCollectionUpdate *const update = [dataSource setCollectionSections:destinationSections];

    XCTestExpectation *expectation = [self expectationWithDescription:@"Update finished"];
    XCTAssertNoThrow([update applyToCollectionView:collectionView completion:^(BOOL finished)
    {
        [expectation fulfill];
    }]);
    [self waitForExpectationsWithTimeout:kUpdateTimeout handler:nil];

    XCTAssertFalse(update.needsReloadData);
    XCTAssertEqual([collectionView numberOfSections], destinationSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:0], destinationItems.count);
}

- (void)testItemComboDeletionReloadMovement {
    // a, b, c, d
    // b, a', d
    NSArray *const sourceItems = @[ [[BasicItem alloc] initWithIdentifier:@"a" title:@"a"], @"b", @"c", @"d" ];
    NSArray *const destinationItems = @[ @"b", [[BasicItem alloc] initWithIdentifier:@"a" title:@"a'"], @"d" ];

    NSArray *const sourceSections = @[CollectionSection(@"a", nil, sourceItems)];
    NSArray *const destinationSections = @[CollectionSection(@"a", nil, destinationItems)];

    MUKDataSource *const dataSource = [[BasicCollectionDataSource alloc] init];
    UICollectionView *const collectionView = [self newCollectionViewWithSections:sourceSections dataSource:dataSource];
    XCTAssertEqual([collectionView numberOfSections], sourceSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:0], sourceItems.count);

    MUKDataSourceCollectionUpdate *const update = [dataSource setCollectionSections:destinationSections];

    NSSet *const deletedItemIndexPaths = [NSSet setWithObjects:IndexPathWithItem(2), nil];
    NSSet *const reloadedItemIndexPaths = [NSSet setWithObjects:IndexPathWithItem(1), nil];
    NSSet *const itemMovements = [NSSet setWithObjects:[[MUKDataSourceContentSectionItemMovement alloc] initWithSourceIndexPath:IndexPathWithItem(0) destinationIndexPath:IndexPathWithItem(1)], nil];

    XCTAssertEqual(update.insertedSectionIndexes.count, 0);
    XCTAssertEqual(update.deletedSectionIndexes.count, 0);
    XCTAssertEqual(update.reloadedSectionIndexes.count, 0);
    XCTAssertEqual(update.sectionMovements.count, 0);

    XCTAssertEqual(update.insertedItemIndexPaths.count, 0);
    XCTAssertEqualObjects(update.deletedItemIndexPaths, deletedItemIndexPaths);
    XCTAssertEqualObjects(update.reloadedItemIndexPaths, reloadedItemIndexPaths);
    XCTAssertEqualObjects(update.itemMovements, itemMovements);

    XCTestExpectation *expectation = [self expectationWithDescription:@"Update finished"];
    XCTAssertNoThrow([update applyToCollectionView:collectionView completion:^(BOOL finished)
    {
        [expectation fulfill];
    }]);
    [self waitForExpectationsWithTimeout:kUpdateTimeout handler:nil];

    XCTAssertFalse(update.needsReloadData);
    XCTAssertEqual([collectionView numberOfSections], destinationSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:0], destinationItems.count);
}

- (void)testItemComboInsertionDeletionReloadMovement {
    // a, b, c
    // b, d, a', e
    NSArray *const sourceItems = @[ [[BasicItem alloc] initWithIdentifier:@"a" title:@"a"], @"b", @"c" ];
    NSArray *const destinationItems = @[ @"b", @"d", [[BasicItem alloc] initWithIdentifier:@"a" title:@"a'"], @"e" ];

    NSArray *const sourceSections = @[CollectionSection(@"a", nil, sourceItems)];
    NSArray *const destinationSections = @[CollectionSection(@"a", nil, destinationItems)];

    MUKDataSource *const dataSource = [[BasicCollectionDataSource alloc] init];
    UICollectionView *const collectionView = [self newCollectionViewWithSections:sourceSections dataSource:dataSource];
    XCTAssertEqual([collectionView numberOfSections], sourceSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:0], sourceItems.count);

    MUKDataSourceCollectionUpdate *const update = [dataSource setCollectionSections:destinationSections];

    NSSet *const insertedItemIndexPaths = [NSSet setWithObjects:IndexPathWithItem(1), IndexPathWithItem(3), nil];
    NSSet *const deletedItemIndexPaths = [NSSet setWithObjects:IndexPathWithItem(2), nil];
    NSSet *const reloadedItemIndexPaths = [NSSet setWithObjects:IndexPathWithItem(2), nil];
    NSSet *const itemMovements = [NSSet setWithObjects:[[MUKDataSourceContentSectionItemMovement alloc] initWithSourceIndexPath:IndexPathWithItem(0) destinationIndexPath:IndexPathWithItem(2)], nil];

    XCTAssertEqual(update.insertedSectionIndexes.count, 0);
    XCTAssertEqual(update.deletedSectionIndexes.count, 0);
    XCTAssertEqual(update.reloadedSectionIndexes.count, 0);
    XCTAssertEqual(update.sectionMovements.count, 0);

    XCTAssertEqualObjects(update.insertedItemIndexPaths, insertedItemIndexPaths);
    XCTAssertEqualObjects(update.deletedItemIndexPaths, deletedItemIndexPaths);
    XCTAssertEqualObjects(update.reloadedItemIndexPaths, reloadedItemIndexPaths);
    XCTAssertEqualObjects(update.itemMovements, itemMovements);

    XCTestExpectation *expectation = [self expectationWithDescription:@"Update finished"];
    XCTAssertNoThrow([update applyToCollectionView:collectionView completion:^(BOOL finished)
    {
        [expectation fulfill];
    }]);
    [self waitForExpectationsWithTimeout:kUpdateTimeout handler:nil];

    XCTAssertFalse(update.needsReloadData);
    XCTAssertEqual([collectionView numberOfSections], destinationSections.count);
    XCTAssertEqual([collectionView numberOfItemsInSection:0], destinationItems.count);
}

#pragma mark - Private

- (UICollectionView *)newCollectionViewWithSections:(NSArray *)sections dataSource:(MUKDataSource *)dataSource
{
    UICollectionViewFlowLayout *const flowLayout = [[UICollectionViewFlowLayout alloc] init];
    UICollectionView *const collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 320.0f, 568.0f) collectionViewLayout:flowLayout];

    [dataSource setCollectionSections:sections];
    collectionView.dataSource = dataSource;

    [collectionView cellForItemAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]]; // access view to initialize it
    [collectionView reloadData];

    return collectionView;
}

static inline MUKDataSourceContentSection *CollectionSection(NSString *identifier, NSString *title, NSArray *items)
{
    NSString *const header = [@"Section: " stringByAppendingString:title ?: [identifier uppercaseString]];
    return [[MUKDataSourceContentSection alloc] initWithIdentifier:identifier items:items ?: @[@""] header:header footer:nil];
}

static inline NSIndexPath *IndexPathWithItem(NSUInteger row) {
    return IndexPath(0, row);
}

static inline NSIndexPath *IndexPath(NSUInteger section, NSUInteger item) {
    return [NSIndexPath indexPathForItem:item inSection:section];
}

@end

redpajama_set_name: RedPajamaGithub


Since I was little, I've loved writing. I've had the opportunity to write for many purposes, including academic work at the college and graduate levels, public health research reports, blogs, health and wellness reporting, and fiction and poetry. I have experience working with editors, conducting on-the-street and expert interviews (in-person and via phone and email), and can quickly research scientific topics and translate them for the consumer.
I've also been giving presentations for several years, both in school and in the community. Past presentations include research summaries, employee in-service training, my undergraduate and masters thesis defense presentations, cooking demonstrations, wellness fair booths, fundraiser booths, and various health topics.
In addition to being an avid bucket lister, I've also worked as a productivity coach and Registered Dietitian Nutritionist.
I have a Master of Public Health in Public Health Nutrition from the University of Washington, and a Bachelor of Science degree in Food Science and Human Nutrition from Washington State University. At WSU, I was also in the Honors College, which focused on writing and communication of all subjects.
Part of my education included over 1200 hours of supervised training in nutrition and experience communicating health information to a variety of people. After graduation, I took a job as a hospital dietitian and used my training and experience to walk with people through lasting behavior change.
Today, I work as a writer and community builder, helping people to move forward toward their goals by connecting them to the information and people they need.
"clear and detailed speaker, very knowledgeable of the subject and gave good answers" -Curtis McKnight, Sr.
Contact me to work with someone who takes your work seriously.


text:
« Archie Fisher — Livestream from the UK
Traditional Irish Slow Session »
In Person AND Live Streamed at Old Songs
Back from a ten-year lunch break, Mad Agnes has reunited to bring you the intricate harmonies and compelling songwriting that have always been their signature. Margo Hennebach, Adrienne Jones and Mark Saunders have been singing and playing together since there were dinosaurs, performing across the U.S. and overseas. While dodging labels and mixing styles (Folk, classical or Celtic? Soaring vocals, incisive lyrics or giggly repartee?), the genre-bending trio has won hearts on two continents, and individually and collectively amassed a body of work which comprises 13 CDs and a concert DVD.
Though each member is a multi-instrumentalist, this iteration sees them pared down with fewer instruments and only one microphone, around which they form and re-form, early-radio style. This allows them to be more playful and more connected, bringing you guitars, mandolin, percussion, keyboard, and "three-part harmonies as tight as jeans from the dryer" (Rob Weir, The Advocate).
madagnes.com
Buy Tickets:
In-Person Concert — COVID-19 Safety Policy: PROOF of VACCINATION and MASKS REQUIRED. Limited Capacity. Read more…
The concert will be held in-person at the Old Songs building and streamed on the Old Songs YouTube Channel. No Refunds.
7:30 pm	EST
Old Songs Community Arts Center
Voorheesville, NY 12186 United States + Google Map
Martin Carthy — Livestream from the UK
Saturday, January 22, at 3:00 pm EST
Archie Fisher — Livestream from the UK
Saturday, February 5, at 3:00 pm EST
Martyn Wyndham-Read — Livestream from the UK
Saturday, February 19, at 3:00 pm EST


Lesley Batchelor OBE is an expert on international trade and a passionate champion of UK exporters. She is also the Director General of the Institute of Export, the professional membership body representing and supporting the interests of everyone involved in importing, exporting and international trade. Here she talks about everything you need to know about getting paid internationally from online banking to checking your customer's credit.
Due to the costs involved in getting paid internationally, it is important to understand how you intend to get paid at the beginning of the sale.
Once you've decided on the right price for your goods use it to quote for the sale. Read this article on pricing strategies for tips and advice about how best to do this.
It's best to take advice at this level. The ICC's model contracts in their web store may help or alternatively speak to your lawyer. You need to ensure that the country in which any dispute is held is compatible with your terms and conditions – ideally with a clause regarding arbitration included which will avoid going straight to litigation if you hit problems.
While in most cases it's best to work with English law in case of a dispute, in some situations it's easier to use the local law where you need action to recoup the monies outstanding.
If you have quoted a customer, that is your offer. The price forms the consideration so that if the customer accepts the quote, there is a contract.
If, however, the acceptance is claused in some way (such as, "we'll take the goods but only if X happens" or they change the terms of delivery) then you need to make sure you're happy with this before you accept/acknowledge the order.
The last piece of correspondence represents the final contract, and therefore binding conditions, of the contract.
Find out what your bank charges for a simple BACs transfer using SWIFT (Societe Worldwide Interbank Financial Transactions) and your IBAN (international Bank Account Number). This cost varies across the world – the USA in particular varies a great deal, being broken down by the private banks and individual states regulations and laws but can sit around £50 -75 so it can impact on your price if that is the way you chose to accept payment. European banks tend to charge less.
Check out this article on all the costs you need to include for more information.
How does it take getting paid internationally?
It's important you find out how long a transfer or a cheque from overseas is likely to take to hit your bank account here. Essentially, you may end up providing an extended credit term on your sale as it can take anywhere from 7 days to 6 weeks to collect the monies depending upon where the issuing bank is. This is the way of international payments.
Checking in with your bank will put you in the picture and help you plan cash flow accordingly. The costs vary with this from around £12.50 – £50.
This has some great advantages as it automatically converts the currency to your home currency and saves the inconvenience of opening a bank account in another currency.
However, it does mean that your price is subject to the daily fluctuations of the international currency exchange rates. Taking control of this will give you better control of your costs.
Also remember that a credit card will cost between 2 and 6 per cent on your sale. Make sure your margin can withstand that additional cost.
You can read more on Open to Export about methods of payment.
A letter of credit is essentially a guarantee from your bank to cover the total value of the sale. Once agreed, the money is held by the buyer's bank so that in the event that your buyer is unable to make payment on the purchase, the bank will cover the agreed amount.
This method is very secure and is often used to support larger transactions. It is costly (around £300-500) – sometimes set against the value of the deal – but for a large transaction it also buys you peace of mind.
Make sure you talk to your bank first and get a template document to send to your customer to complete.
Next, make sure that you can do all the steps that the customer and their bank has asked of you in terms of packing, documentation and shipping.
For more details about letters of credit, see our article on Letters of credit explained.
There is also something called a 'Standby Letter of Credit' which is less costly and can be a great way of gaining the security of a letter of credit without the detail. Ask your bank how they work.
This webinar on how to get paid on time and in full provides further tips and guidance on the technicalities of getting paid when exporting.
Previous Post What costs should I include? Next Post Which international sales channels are appropriate for me?


text:
Local elections were held in Northern Ireland on Thursday 22 May 2014, contesting 462 seats in all, as part of the wider local elections across the United Kingdom. The election took place on the same day as the European Parliament election. 1,243,649 people aged 18 and over were eligible to vote, and 51.3% of the electorate turned out.

Voter eligibility
All voters were required to present one piece of photographic ID in order to cast a vote at the polling station. Accepted forms of ID were an electoral identity card, an EEA photographic driving licence, a European Union member-state passport, a Translink 60+ SmartPass, a Translink Senior SmartPass, a Translink Blind Person's SmartPass or a Translink War Disabled SmartPass. Voters lacking an accepted type of photographic ID had until 9 May 2014 to apply for an electoral identity card from the Electoral Office.

Background
The elections represented a milestone in the reform of local government in Northern Ireland, as councillors were elected to 11 new councils. These operated in shadow form until Wednesday 1 April 2015, with the current 26 councils existing in parallel until then.

The 11 new councils, with links to the official lists of candidates standing ("statements of persons nominated"), are:
Belfast City Council (Candidates)
North Down and Ards District Council (Candidates)
Antrim and Newtownabbey District Council (Candidates)
Lisburn and Castlereagh District Council (Candidates)
Newry, Mourne and Down District Council (Candidates)
Armagh, Banbridge and Craigavon District Council (Candidates)
Mid and East Antrim District Council (Candidates)
Causeway Coast and Glens District Council (Candidates)
Mid-Ulster District Council (Candidates)
Derry and Strabane District Council (Candidates)
Fermanagh and Omagh District Council (Candidates)

The local government reorganisation and electoral administration was mandated by the Local Government Act (Northern Ireland) 2014. Nominations of election candidates closed on 29 April 2014.

Results
Because these elections were contested with new electoral boundaries, the results are not directly comparable with those of the last election. However, psephologist Nicholas Whyte has calculated a baseline by which to judge the parties' relative performance. This baseline is used in the following tables.

Results by council

Belfast

Results by party

Councils

Antrim and Newtownabbey

Armagh, Banbridge and Craigavon

Belfast

Causeway Coast and Glens

Derry and Strabane

Fermanagh and Omagh

Lisburn and Castlereagh

Mid and East Antrim

Mid Ulster

Newry, Mourne and Down

North Down and Ards

References

2014

redpajama_set_name: RedPajamaWikipedia


Hair and make-up salon in Co. Dublin offering real hair extension systems. Stockists of complete Sabastian hair range including Xtah, Laminates and Collection plus the fabulous Truco make-up range exclusive to Pzazz. Indian head massage given as part of service.


text:
package main

import (
	"errors"
	"expvar"
	"io"
	"log"
	"net"
	"sync/atomic"

	tls "github.com/jmhodges/howsmyssl/tls110"
)

var (
	_              net.Listener = &listener{}
	_              net.Conn     = &conn{}
	errTLSConnConv              = errors.New("Unable to convert net.Conn to tls.Conn")
)

type listener struct {
	net.Listener
	*handshakeStats
}

type handshakeStats struct {
	Successes       *expvar.Int
	Errs            *expvar.Int
	ReadTimeouts    *expvar.Int
	WriteTimeouts   *expvar.Int
	UnknownTimeouts *expvar.Int
	EOFs            *expvar.Int
}

func newHandshakeStats(ns *expvar.Map) *handshakeStats {
	s := &handshakeStats{
		Successes:       &expvar.Int{},
		Errs:            &expvar.Int{},
		ReadTimeouts:    &expvar.Int{},
		WriteTimeouts:   &expvar.Int{},
		UnknownTimeouts: &expvar.Int{},
		EOFs:            &expvar.Int{},
	}
	ns.Set("successes", s.Successes)
	ns.Set("errors", s.Errs)
	ns.Set("read_timeouts", s.ReadTimeouts)
	ns.Set("write_timeouts", s.WriteTimeouts)
	ns.Set("unknown_timeouts", s.UnknownTimeouts)
	ns.Set("eofs", s.EOFs)
	return s
}
func newListener(nl net.Listener, ns *expvar.Map) *listener {
	statNS := new(expvar.Map).Init()
	ns.Set("handshake", statNS)
	lis := &listener{
		Listener:       nl,
		handshakeStats: newHandshakeStats(statNS),
	}
	return lis
}

func (l *listener) Accept() (net.Conn, error) {
	c, err := l.Listener.Accept()
	if err != nil {
		return c, err

	}
	tlsConn, ok := c.(*tls.Conn)
	if !ok {
		c.Close()
		return nil, errTLSConnConv
	}
	return &conn{
		Conn:           tlsConn,
		handshakeStats: l.handshakeStats,
	}, nil
}

type conn struct {
	*tls.Conn
	handshakeCounted int32
	*handshakeStats
}

func (c *conn) Read(b []byte) (int, error) {
	err := c.handshake()
	if err != nil {
		return 0, err
	}
	return c.Conn.Read(b)
}

func (c *conn) Write(b []byte) (int, error) {
	err := c.handshake()
	if err != nil {
		return 0, err
	}
	return c.Conn.Write(b)
}

// This, unfortunately, means we take two uncontended locks on every read and
// write: the c.handshakeMutex here and the one in tls.Conn.
func (c *conn) handshake() error {
	alreadyCounted := !atomic.CompareAndSwapInt32(&c.handshakeCounted, 0, 1)
	if alreadyCounted {
		return nil
	}

	err := c.Conn.Handshake()
	if err != nil {
		return err
	}
	if !alreadyCounted {
		c.Successes.Add(1)
	}
	return nil
}

func (c *conn) errorToStats(err error) {
	if err != nil {
		c.Errs.Add(1)
		if err == io.EOF {
			c.EOFs.Add(1)
		} else if opErr, ok := err.(*net.OpError); ok && opErr.Timeout() {
			switch opErr.Op {
			case "read":
				c.ReadTimeouts.Add(1)
			case "write":
				c.WriteTimeouts.Add(1)
			default:
				log.Printf("unknown timeout type: %s", opErr.Op)
				c.UnknownTimeouts.Add(1)
			}
		} else {
			log.Printf("unknown tls error: %s", err)
		}
	}
}