Giter Site home page Giter Site logo

tcldr / entwine Goto Github PK

View Code? Open in Web Editor NEW
440.0 9.0 25.0 7.53 MB

Testing tools and utilities for Apple's Combine framework.

License: MIT License

Swift 99.53% Awk 0.43% Makefile 0.04%
combine ios swift tvos swiftui test xctest apple combine-framework reactive

entwine's People

Contributors

adrianbindc avatar econa77 avatar heckj avatar sharplet avatar tcldr avatar yuzushioh avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

entwine's Issues

Testing a publisher on main thread seems to break test

We are testing a viewModel with a boolean property

@Published var isShown: Bool = false

This value is set by a publisher to which the viewModel subscribes on the Runloop.main

    service
        .observe()
        .receive(on: RunLoop.main)
        .sink(receiveCompletion: { _ in })
        { toast in
            if let toast = toast {
                self.message = toast.message
                self.isShown = true
            } else {
                self.message = ""
                self.isShown = false
            }

This causes our tests to not receive any update.

    let scheduler = TestScheduler()
    let results = scheduler.start {
        self.underTest.$isShown
    }.recordedOutput

    expect(results).to(equal(
        [
            (200, .subscription),
            (200, .input(false)),
            (300, .input(true)),
            (400, .input(false)),
        ]
    ))

If we remove .receive(on: RunLoop.main) the test succeeds.

Is there any chance to add the ability to observe the test on a different thread? Or is there a mistake in our test setup?

Test Publishers.CollectByTime with TestScheduler

I've started to test various Combine Publishers and got stuck with Publishers.CollectByTime. I'm using TestScheduler and VirtualTimeInterval to test it. I might got it all wrong, but I expect the publisher to buffer and periodically publish arrays of signals. The first array is published correctly and it respects the VirtualTimeInterval. But then ALL other signals are published in the second array and VirtualTime of each signal is ignored.

With VirtualTimeInterval = 2 I publish

(1, .input("a")),
(2, .input("b")),
(4, .input("c")),
(5, .input("d")),
(7, .input("e")),
(8, .input("f"))

I expect Publishers.CollectByTime to group signals this way: ["ab", "cd", "ef"]. But I get ["ab", "cdef"].
Can you please tell me what is wrong with my code or maybe with my understanding the CollectByTime publisher?

import Combine
import Entwine
import EntwineTest
import XCTest

final class CollectByTime: XCTestCase {

   func test_common_behavior() {
      let configuration = TestScheduler.Configuration.default
      let scheduler = TestScheduler()
      let upstream: TestablePublisher<String, Never> = scheduler.createRelativeTestablePublisher([
         (1, .input("a")),
         (2, .input("b")),
         (4, .input("c")),
         (5, .input("d")),
         (7, .input("e")),
         (8, .input("f")),
         (9, .completion(.finished))
      ])
      let window: VirtualTimeInterval = 2
      let strategy = Publishers.TimeGroupingStrategy<TestScheduler>
         .byTime(scheduler, window)
      let publisher = Publishers.CollectByTime(
         upstream: upstream,
         strategy: strategy,
         options: nil
      )
      let subscriber = scheduler.start(configuration: configuration, create: { publisher })
      let values = subscriber
         .recordedOutput
         .compactMap({ _, signal -> String? in
            switch signal {
            case .input(let array):
               return array.joined()
            default:
               return nil
            }
         })
      XCTAssertEqual(values, ["ab", "cd", "ef"])
   }
}

Entwine doesn't appear to be happy with Xcode 11.2/Swift 5.1

I just updated Xcode to the 11.2 release, and while I suspect you already know, it seems there's some quirks that make Entwine a little less effective. Tests that passed previously are exploding now - I'm getting an EXC_BAD_ACCESS on TestablePublisherSubscription - I wasn't sure how to capture the information to best share, so I grabbed a screenshot Xcode dropping into the debugger. I'll poke a bit more, as maybe it's something stupid I've done - but I suspect it's in the queue'd side effects of Xcode 11.2 release

Screen Shot 2019-11-02 at 11 52 41 AM

ReplaySubject inconsistent behavior ?

Consider the following code involving ReplaySubject:

    // let stream = Just(1).print("Debug: ")
    let stream = Just(1).print("Debug: ").share(replay: 1)

    let subscription1 = stream.sink(receiveValue: {
        print("Receive: \($0)")
    })
    
    subscription1.cancel()
    
    let subscription2 = stream.sink(receiveValue: {
        print("Receive: \($0)")
    })
    
    subscription2.cancel()

Only subscription1 will receive values. This is quite clear because ReplaySubject goes to complete state when uplink completes.

Issue 1:
All late subscribers will receive nothing but completion.

Issue 2:
Though internally ReplaySubject keeps references to data in buffer which will never be accessed by late subscribers and I would consider it a memory leak.

I believe ReplaySubject should behave differently:

  1. When there are no subscribers we should disconnect upstream and cleanup buffer. Thus we get back to absolutely cold state how it was before first subscription. And data which is not accessible will be dereferenced.

  2. When new subscriber comes we connect upstream and start from scratch.

  3. When upstream completes we shouldn't propagate completion but still serve data from buffer to late subscribers.

I don't know how it works in RxSwift but in RxJava:

   .replay(1).refcount()

Works just how I described it.
In RxJs it's quite complex and you need to write:

  .multicast(() => new ReplaySubject(1)).refCount()

I think since RxJS 6.4.0 it's equal to:

 .shareReplay({refCount: true, bufferSize: 1})

In both cases when there are no subscribers data inside subject will be dereferenced and upstream will be cancelled. When new subscriber comes it starts from scratch.

I know it's a huge holywar in RxJs on this subjects.
There's a great article showing the differences: here

Could you comment on this please so we can brainstorm together.

Unable to create `TestScheduler.Configuration`

I have a test that had quite a few publishes (7, to be exact) and I wanted to change the configuration to cancel at 1000 rather than 900, but I was unable to create my own configuration because of the following build error:

'TestScheduler.Configuration' initializer is inaccessible due to 'internal' protection level

I believe this will require an explicit init() marked with public for TestScheduler.Configuration.

Add toBlocking operator like RxSwift

RxSwift includes and useful operator that transform any observable into a blocking sequence.

For Combine it'll be really useful being able to implement the same behavior. Here's how it works on RxSwift http://rx-marin.com/post/rxblocking-part1/

I got something working using current code but it's far from ready, it may help us getting the idea of the expected behavior:

let testScheduler = TestScheduler()

let publisher = TestPublisher<Int, TestError>.init { subscriber in
    subscriber.receive(1)
    subscriber.receive(2)
}

let configuration = TestScheduler.Configuration(pausedOnStart: false, created: 0, subscribed: 0, cancelled: 1, subscriberOptions: TestableSubscriberOptions(initialDemand: Subscribers.Demand.unlimited, subsequentDemand: Subscribers.Demand.unlimited, demandReplenishmentDelay: 0, negativeBalanceHandler: { }))

let testableSubscriber = testScheduler.start(configuration: configuration) { publisher }

XCTAssertEqual(testableSubscriber.sequence, [
    (0, .input(1)),
    (0, .input(2)),
    (0, .subscription),
])

The expected behavior would be:

let publisher = TestPublisher<Int, TestError>.init { subscriber in
    subscriber.receive(1)
    subscriber.receive(2)
}

let blockingPublisher = publisher.toBlocking()

XCTAssertEqual(blockingPublisher.sequence, [1, 2])

This will allow testing Combine publishers in libraries like https://github.com/bitomule/CombineRealm

Here's the RxSwift implementation: https://github.com/ReactiveX/RxSwift/tree/master/RxBlocking

consider changing TestSubscriber.sequence to something else

In using TestSubscriber, I found the property name sequence to be confusing. While it makes sense in the framing of what's been collection, and that it's an ordered list, I immediately confused it for Combine's Sequence.

I wanted to suggest that perhaps a different name would be easier to work with - although I'm lacking on brilliant ideas. My fogged brain only really came up with results or orderedResults as alternative property names.

using EntwineTest to compare results when the underlying type is a tuple - kind of awkward

First off - I'm the last to say I'm an expert at Swift, or the vagaries of type theory and effectively using a statically type language with generics like we have.

I was creating some tests to using EntwineTests virtual scheduler to illuminate how some of the Combine operators work - specifically combineLatest and some of the others that merge pipelines together. (ref: heckj/swiftui-notes#56)

What I hit my shins on was that the equatable conformance with Entwine's Signal relies entirely on the underlying type's conformance - totally good, except that the type can be a tuple (and is, in the case of combineLatest, which is merging the output types of the upstream pipelines.

I came up with two options to do the validation regardless, although both are really quite ugly hacks. I'm opening this issue to see if you had any brilliant insights into how this might be done more nicely.

The first was the use the same testing hack I use for comparing Error enums - using the debugDescription() method on the enumeration to generate a string, and comparing that. It's stringly-type-erasing the compiler details away, while providing fairly useful tests.

The second was to create a one-off function that specifically broke down a TestSequence item (itself a tuple of VirtualTime and a Signal enum instance) and return a boolean value if they match up.

Although it's functioning and correct, it feels ugly and awkward, so I was hoping you might have some ideas on how this might be made more elegant.

Although the timing was very useful to show for my combineLatest tests, I also realized that the key of what I was interested in validating was the ordered list of data results, and a lot of the type information around it wasn't as interesting for my specific test.

I'm not sure if there'd be significant value in making a helper operator to pull out just a sequence of the OutputType and make that available from under TestSequence, but it might be useful as an idea.

Feature request: proper switchToLatest and switchMap operators workaround

Hi!

Consider the following code:

cancellable = Just(2).map { x in
    Just(x * x).delay(for: 2.0, scheduler: RunLoop.main)
}
.switchToLatest()
.sink(receiveCompletion: {_ in
    print("completed")
}, receiveValue: {result in
    print(result)
})

In this example I tried to mimic switchMap operator with map+switchToLatest and surprisingly none of sink callbacks will be ever called. This is a bug in Combine I believe.

Looks like switchToLatest cancels immediately when upstream completes and doesn't wait for inner subscription to complete.

Surprisingly the same example works with a flatMap.

I suggest to write a proper switchToLatest operator and switchMap as well. Here's a code I created today for you to consider. Maybe there's an easier solution?

extension Publisher where Self.Output : Publisher, Self.Output.Failure == Failure {
    func switchToLatestWaitable() -> Publishers.SwitchToLatestWaitable<Self> {
        Publishers.SwitchToLatestWaitable(upstream: self)
    }
}

extension Publishers {
    
    public struct SwitchToLatestWaitable<Upstream: Publisher>: Publisher where Upstream.Output : Publisher, Upstream.Output.Failure == Upstream.Failure {
        public typealias Output = Upstream.Output.Output
        public typealias Failure = Upstream.Failure
        
        private let upstream: Upstream
        
        init(upstream: Upstream) {
            self.upstream = upstream
        }
        
        public func receive<S: Subscriber>(subscriber: S) where Failure == S.Failure, Output == S.Input {
            upstream.receive(subscriber: SwitchToLatestWaitableSubscriber(subscriber))
        }
        
        class SwitchToLatestWaitableSubscriber<Downstream: Subscriber>: Subscriber where Downstream.Input == Upstream.Output.Output, Downstream.Failure == Upstream.Failure {
            public typealias Input = Upstream.Output
            public typealias Failure = Upstream.Failure
            
            private var downstream: Downstream
            private var upstreamCompleted = false
            private var downstreamCompleted = false
            private var innerCancellable: AnyCancellable? = nil
            
            init(_ downstream: Downstream){
                self.downstream = downstream
            }
            
            func receive(subscription: Subscription) {
                downstream.receive(subscription: subscription)
            }
            
            func receive(_ input: Input) -> Subscribers.Demand {
                innerCancellable = input.sink(receiveCompletion: {completion in
                    lock(obj: self) {
                        switch completion {
                        case .finished:
                            if self.upstreamCompleted {
                                self.complete(completion)
                            }
                        case .failure:
                            self.complete(completion)
                        }
                    }
                    }, receiveValue: {input in
                        lock(obj: self) {
                            _ = self.downstream.receive(input)
                        }
                })
                
                return .unlimited
            }
            
            func receive(completion: Subscribers.Completion<Failure>) {
                lock(obj: self){
                    switch completion {
                    case .finished:
                        upstreamCompleted = true
                        
                    case .failure:
                        // Immediately pass failure
                        complete(completion)
                    }
                }
            }
            
            private func complete(_ completion: Subscribers.Completion<Failure>){
                if !downstreamCompleted {
                    downstream.receive(completion: completion)
                    downstreamCompleted = true
                    innerCancellable = nil
                }
            }
            
        }
    }
}



`swift test` failing out of the box with the repo on Mojave

I cloned the repo and immediately tried:

  • swift build
  • swift test

it builds just fine, but on MacOS 10.14.5 (Mojave), the swift test fails, even with the correct swift being invoked:

swift -v:

Apple Swift version 5.1 (swiftlang-1100.0.43.3 clang-1100.0.26.3)
Target: x86_64-apple-darwin18.6.0
/Users/heckj/Applications/Xcode-beta.app/Contents/Developer/usr/bin/lldb "--repl=-enable-objc-interop -sdk /Users/heckj/Applications/Xcode-beta.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk -color-diagnostics"

And the failure: swift test

2019-07-01 21:44:52.259 xctest[9733:44482] The bundle “EntwinePackageTests.xctest” couldn’t be loaded because it is damaged or missing necessary resources. Try reinstalling the bundle.
2019-07-01 21:44:52.259 xctest[9733:44482] (dlopen_preflight(/Users/heckj/src/Entwine/.build/x86_64-apple-macosx/debug/EntwinePackageTests.xctest/Contents/MacOS/EntwinePackageTests): Library not loaded: /System/Library/Frameworks/Combine.framework/Versions/A/Combine
  Referenced from: /Users/heckj/src/Entwine/.build/x86_64-apple-macosx/debug/EntwinePackageTests.xctest/Contents/MacOS/EntwinePackageTests
  Reason: image not found)

This may be something where swift test isn't respecting the platform constraint that you have defined in Package.swift (.macOS(.v10_15)), but I wasn't entire sure - so thought I'd mention it here.

Unexpected behaviour/result from TestableSubscriber

I'm tinkering with persistence middleware for an app which uses Redux-like architecture using Realm as a backend. Results from queries on the database should be emitted by a Combine Publisher.

Although Realm now supports Combine and provides FrozenCollection types to manage threading and concurrency-related errors, I've been advised to use DTOs (structs) to make things as predictable as possible. I've written a test which utilises Entwine to ensure that the conversion of the Realm FrozenCollection to the DTO behaves as expected but the TestableSubscriber receives an empty array at time index 0 when it should in fact receive three inputs (initial empty array at t=0, test set of 3 elements at t = 100 and a second test set of 3 elements at t = 200).

I've reverted to testing using an Expectation as well as debugging the Publisher using print() and get the expected output (6 elements emitted in total, conversion to DTO working OK) so am working on the assumption that I am doing something wrong although I guess there is the outside possibility there's a bug?

Currently using Xcode12, iOS14. Code as follows:

Middleware (SUT)

import Foundation
import Combine

import RealmSwift

final class PersistenceMiddleware {
    private var cancellables = Set<AnyCancellable>()
    private var subject = CurrentValueSubject<[ToDo.DTO], Never>([])
    
    func allToDos(in realm: Realm = try! Realm()) -> AnyPublisher<[ToDo.DTO], Never> {
        realm.objects(ToDo.self)
            .collectionPublisher
//            .print()
            .assertNoFailure()
            .freeze()
            .map { item in
                item.map { $0.convertToDTO() }
            }
//            .print()
            .receive(on: DispatchQueue.main)
            .subscribe(subject)
            .store(in: &cancellables)
            
        return subject.eraseToAnyPublisher()
    }
    
    
    deinit {
        print("Deinit")
        cancellables = []
    }
}

Test class

import XCTest
import Combine

import RealmSwift
import EntwineTest

@testable import SwiftRex_ToDo_Persisted

class SwiftRex_ToDo_PersistedTests: XCTestCase {
    private var realm: Realm?
    
    private var testSet1: [ToDo] {
        [
            ToDo(name: "Mow lawn"),
            ToDo(name: "Wash car"),
            ToDo(name: "Clean windows")
        ]
    }
    
    private var testSet2: [ToDo] {
        [
            ToDo(name: "Walk dog"),
            ToDo(name: "Cook dinner"),
            ToDo(name: "Pay bills")
        ]
    }
    
    override func setUpWithError() throws {
        // Put setup code here. This method is called before the invocation of each test method in the class.
        realm = try! Realm(configuration: Realm.Configuration(inMemoryIdentifier: UUID().uuidString))
    }
    
    override func tearDownWithError() throws {
        // Put teardown code here. This method is called after the invocation of each test method in the class.
        realm = nil
    }
    
    func testCorrectNumberOfObjectsStoredInRealm() {
        realm!.addForTesting(objects: testSet1)
        XCTAssertEqual(realm!.objects(ToDo.self).count, 3)
        
        realm!.addForTesting(objects: testSet2)
        XCTAssertEqual(realm!.objects(ToDo.self).count, 6)
    }
    
    func testMiddlewarePublisherUsingEntwine() {
        let middleware = PersistenceMiddleware()
        let scheduler = TestScheduler()
        let subscriber = scheduler.createTestableSubscriber([ToDo.DTO].self, Never.self)

        middleware.allToDos(in: self.realm!)
            .subscribe(subscriber)
        
        scheduler.schedule(after: 100) { self.realm!.addForTesting(objects: self.testSet1) }
        scheduler.schedule(after: 200) { self.realm!.addForTesting(objects: self.testSet2) }

        scheduler.resume()

        print("\(subscriber.recordedOutput)")
    }
    
    func testMiddlewarePublisherUsingExpectation() {
        let middleware = PersistenceMiddleware()
        var cancellables = Set<AnyCancellable>()
        let receivedValues = expectation(description: "received expected number of published objects")
        
        middleware.allToDos(in: realm!)
            .sink { result in
                if result.count == 6 {
                    NSLog(result.debugDescription)
                    receivedValues.fulfill()
                }
            }
            .store(in: &cancellables)

        realm!.addForTesting(objects: testSet1)
        realm!.addForTesting(objects: testSet2)

        waitForExpectations(timeout: 1, handler: nil)
    }
    
}

Console output
Test Suite 'All tests' started at 2020-10-03 15:49:39.166
Test Suite 'SwiftRex-ToDo-PersistedTests.xctest' started at 2020-10-03 15:49:39.167
Test Suite 'SwiftRex_ToDo_PersistedTests' started at 2020-10-03 15:49:39.167
Test Case '-[SwiftRex_ToDo_PersistedTests.SwiftRex_ToDo_PersistedTests testCorrectNumberOfObjectsStoredInRealm]' started.
2020-10-03 15:49:39.182753+0100 SwiftRex-ToDo-Persisted[14502:643475] Version 5.4.7 of Realm is now available: https://github.com/realm/realm-cocoa/blob/v5.4.7/CHANGELOG.md
2020-10-03 15:49:39.231129+0100 SwiftRex-ToDo-Persisted[14502:643297] Setup
2020-10-03 15:49:39.232989+0100 SwiftRex-ToDo-Persisted[14502:643297] Tear down
/n
Test Case '-[SwiftRex_ToDo_PersistedTests.SwiftRex_ToDo_PersistedTests testCorrectNumberOfObjectsStoredInRealm]' passed (0.067 seconds).
Test Case '-[SwiftRex_ToDo_PersistedTests.SwiftRex_ToDo_PersistedTests testMiddlewarePublisherUsingEntwine]' started.
2020-10-03 15:49:39.262702+0100 SwiftRex-ToDo-Persisted[14502:643297] Setup
2020-10-03 15:49:39.264956+0100 SwiftRex-ToDo-Persisted[14502:643474] [] nw_protocol_get_quic_image_block_invoke dlopen libquic failed
TestSequence<Array, Never>(contents: [(0, .subscribe), (0, .input([]))])
Deinit
2020-10-03 15:49:39.267422+0100 SwiftRex-ToDo-Persisted[14502:643297] Tear down
/n
Test Case '-[SwiftRex_ToDo_PersistedTests.SwiftRex_ToDo_PersistedTests testMiddlewarePublisherUsingEntwine]' passed (0.033 seconds).
Test Case '-[SwiftRex_ToDo_PersistedTests.SwiftRex_ToDo_PersistedTests testMiddlewarePublisherUsingExpectation]' started.
2020-10-03 15:49:39.272248+0100 SwiftRex-ToDo-Persisted[14502:643297] Setup
2020-10-03 15:49:39.274820+0100 SwiftRex-ToDo-Persisted[14502:643297] [SwiftRex_ToDo_Persisted.ToDo.DTO(id: 0, name: "Mow lawn"), SwiftRex_ToDo_Persisted.ToDo.DTO(id: 1, name: "Wash car"), SwiftRex_ToDo_Persisted.ToDo.DTO(id: 2, name: "Clean windows"), SwiftRex_ToDo_Persisted.ToDo.DTO(id: 3, name: "Walk dog"), SwiftRex_ToDo_Persisted.ToDo.DTO(id: 4, name: "Cook dinner"), SwiftRex_ToDo_Persisted.ToDo.DTO(id: 5, name: "Pay bills")]
Deinit
2020-10-03 15:49:39.275235+0100 SwiftRex-ToDo-Persisted[14502:643297] Tear down
/n
Test Case '-[SwiftRex_ToDo_PersistedTests.SwiftRex_ToDo_PersistedTests testMiddlewarePublisherUsingExpectation]' passed (0.008 seconds).
Test Suite 'SwiftRex_ToDo_PersistedTests' passed at 2020-10-03 15:49:39.277.
Executed 3 tests, with 0 failures (0 unexpected) in 0.108 (0.109) seconds
Test Suite 'SwiftRex-ToDo-PersistedTests.xctest' passed at 2020-10-03 15:49:39.277.
Executed 3 tests, with 0 failures (0 unexpected) in 0.108 (0.110) seconds
Test Suite 'All tests' passed at 2020-10-03 15:49:39.292.
Executed 3 tests, with 0 failures (0 unexpected) in 0.108 (0.126) seconds

ReplaySubject shouldn't keep it's values after all subscriptions are removed

Hello again!
Please consider the following code in RxJava:

    val subj = BehaviorSubject.createDefault(0)
    val stream = subj.switchMap {
        print("Inside map\n")
        Observable.just(1)
    }.replay(1).refCount()

    val disposable1 = stream.subscribe {
        print("Stream 1 $it\n")
    }

    disposable1.dispose()

    val disposable2 = stream.subscribe {
        print("Stream 2 $it\n")
    }

    disposable2.dispose()

It outputs:

Inside map
Stream 1 1
Inside map
Stream 2 1

Now let's check how Combine+Entwine works:

    let source = CurrentValueSubject<Bool, Never>(true)
    let stream = source.map{ _ -> AnyPublisher<Int, Never> in
        print("Inside map")
        return Just(1).eraseToAnyPublisher()
    }.switchToLatest()
     .share(replay: 1);

    let disposable1 = stream.sink(receiveValue: {
        print("Stream 1 \($0)")
    })

    disposable1.cancel()

    let disposable2 = stream.sink(receiveValue:  {
        print("Stream 2 \($0)")
    } )

    disposable2.cancel()

It outputs:

Inside map
Stream 1 1
Stream 2 1
Inside map
Stream 2 1

Here's the difference: Stream 2 received values twice which is wrong I believe.
This works properly if Source completes.
I believe the difference is that Combine doesn't recreate a Subject in case there are no subscribers left but Source is not completed.
Though RxJava uses new Subject in this case.

I suggest to clean data inside Subject when there are no subscribers left to be consistent.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.