अपरिवर्तनीय उल्लंघन: "कनेक्ट" (SportsDatabase) के संदर्भ या प्रॉप्स में "स्टोर" नहीं मिला।


142

पूरा कोड यहाँ: https://gist.github.com/js08/0ec3d70dfda76d7e9fb4

नमस्ते,

  • मेरे पास एक एप्लिकेशन है जहां यह बिल्ड वातावरण के आधार पर डेस्कटॉप और मोबाइल के लिए विभिन्न टेम्पलेट्स दिखाता है।
  • मैं इसे सफलतापूर्वक विकसित करने में सक्षम हूं जहां मुझे अपने मोबाइल टेम्पलेट के लिए नेविगेशन मेनू को छिपाने की आवश्यकता है।
  • अभी मैं एक परीक्षण मामले को लिखने में सक्षम हूं, जहां यह प्रॉपटीज़ के माध्यम से सभी मूल्यों को प्राप्त करता है और सही ढंग से प्रस्तुत करता है
  • लेकिन यह निश्चित नहीं है कि इकाई परीक्षण के मामलों को कैसे लिखा जाए जब उसके मोबाइल को नौसेना के घटक को प्रस्तुत नहीं करना चाहिए।
  • मैंने कोशिश की लेकिन मैं एक त्रुटि का सामना कर रहा हूं ... क्या आप मुझे बता सकते हैं कि इसे कैसे ठीक किया जाए।
  • नीचे कोड साबित हो रहा है।

परीक्षण का मामला

import {expect} from 'chai';
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import {SportsTopPortion} from '../../../src/components/sports-top-portion/sports-top-portion.jsx';
require('../../test-utils/dom');


describe('"sports-top-portion" Unit Tests', function() {
    let shallowRenderer = TestUtils.createRenderer();

    let sportsContentContainerLayout ='mobile';
    let sportsContentContainerProfile = {'exists': 'hasSidebar'};
    let sportsContentContainerAuthExchange = {hasValidAccessToken: true};
    let sportsContentContainerHasValidAccessToken ='test'; 

    it('should render correctly', () => {
        shallowRenderer.render(<SportsTopPortion sportsWholeFramework={sportsContentContainerLayout} sportsPlayers={sportsContentContainerProfile} sportsAuthentication={sportsContentContainerAuthExchange} sportsUpperBar={{activeSportsLink:'test'}} />);
        //shallowRenderer.render(<SportsTopPortion sportsWholeFramework={sportsContentContainerLayout} sportsPlayers={sportsContentContainerProfile} hasValidAccessToken={sportsContentContainerHasValidAccessToken}  />);

        let renderedElement = shallowRenderer.getRenderOutput();
        console.log("renderedElement------->" + JSON.stringify(renderedElement));

        expect(renderedElement).to.exist;
    });

    it('should not render sportsNavigationComponent when sports.build is mobile', () => {
        let sportsNavigationComponent = TestUtils.renderIntoDocument(<SportsTopPortion sportsWholeFramework={sportsContentContainerLayout} sportsPlayers={sportsContentContainerProfile} sportsAuthentication={sportsContentContainerAuthExchange} sportsUpperBar={{activeSportsLink:'test'}} />);
        console.log("sportsNavigationComponent------->" + JSON.stringify(sportsNavigationComponent));

        //let footnoteContainer = TestUtils.findRenderedDOMComponentWithClass(sportsNavigationComponent, 'linkPack--standard');

        //expect(footnoteContainer).to.exist;
    });

});

कोड स्निपेट जहां परीक्षण मामले को लिखा जाना चाहिए

if (sports.build === 'mobile') {
    sportsNavigationComponent = <div />;
    sportsSideMEnu = <div />;
    searchComponent = <div />;
    sportsPlayersWidget = <div />;
}

त्रुटि

1) "sports-top-portion" Unit Tests should not render sportsNavigationComponent when sports.build is mobile:
     Invariant Violation: Could not find "store" in either the context or props of "Connect(SportsDatabase)". Either wrap the root component in a <Provider>, or explicitly pass "store" as a prop to "Connect(SportsDatabase)".
      at Object.invariant [as default] (C:\sports-whole-page\node_modules\invariant\invariant.js:42:15)
      at new Connect (C:\sports-whole-page\node_modules\react-redux\lib\components\createConnect.js:135:33)
      at [object Object].ReactCompositeComponentMixin.mountComponent (C:\sports-whole-page\node_modules\react\lib\ReactCompositeComponent.js:148:18)
      at [object Object].wrapper [as mountComponent] (C:\sports-whole-page\node_modules\react\lib\ReactPerf.js:66:21)
      at Object.ReactReconciler.mountComponent (C:\sports-whole-page\node_modules\react\lib\ReactReconciler.js:37:35)
      at ReactDOMComponent.ReactMultiChild.Mixin.mountChildren (C:\sports-whole-page\node_modules\react\lib\ReactMultiChild.js:241:44)
      at ReactDOMComponent.Mixin._createContentMarkup (C:\sports-whole-page\node_modules\react\lib\ReactDOMComponent.js:591:32)
      at ReactDOMComponent.Mixin.mountComponent (C:\sports-whole-page\node_modules\react\lib\ReactDOMComponent.js:479:29)
      at Object.ReactReconciler.mountComponent (C:\sports-whole-page\node_modules\react\lib\ReactReconciler.js:37:35)
      at ReactDOMComponent.ReactMultiChild.Mixin.mountChildren (C:\sports-whole-page\node_modules\react\lib\ReactMultiChild.js:241:44)
      at ReactDOMComponent.Mixin._createContentMarkup (C:\sports-whole-page\node_modules\react\lib\ReactDOMComponent.js:591:32)
      at ReactDOMComponent.Mixin.mountComponent (C:\sports-whole-page\node_modules\react\lib\ReactDOMComponent.js:479:29)
      at Object.ReactReconciler.mountComponent (C:\sports-whole-page\node_modules\react\lib\ReactReconciler.js:37:35)
      at [object Object].ReactCompositeComponentMixin.mountComponent (C:\sports-whole-page\node_modules\react\lib\ReactCompositeComponent.js:225:34)
      at [object Object].wrapper [as mountComponent] (C:\sports-whole-page\node_modules\react\lib\ReactPerf.js:66:21)
      at Object.ReactReconciler.mountComponent (C:\sports-whole-page\node_modules\react\lib\ReactReconciler.js:37:35)
      at ReactDOMComponent.ReactMultiChild.Mixin.mountChildren (C:\sports-whole-page\node_modules\react\lib\ReactMultiChild.js:241:44)
      at ReactDOMComponent.Mixin._createContentMarkup (C:\sports-whole-page\node_modules\react\lib\ReactDOMComponent.js:591:32)
      at ReactDOMComponent.Mixin.mountComponent (C:\sports-whole-page\node_modules\react\lib\ReactDOMComponent.js:479:29)
      at Object.ReactReconciler.mountComponent (C:\sports-whole-page\node_modules\react\lib\ReactReconciler.js:37:35)
      at [object Object].ReactCompositeComponentMixin.mountComponent (C:\sports-whole-page\node_modules\react\lib\ReactCompositeComponent.js:225:34)
      at [object Object].wrapper [as mountComponent] (C:\sports-whole-page\node_modules\react\lib\ReactPerf.js:66:21)
      at Object.ReactReconciler.mountComponent (C:\sports-whole-page\node_modules\react\lib\ReactReconciler.js:37:35)
      at ReactDOMComponent.ReactMultiChild.Mixin.mountChildren (C:\sports-whole-page\node_modules\react\lib\ReactMultiChild.js:241:44)
      at ReactDOMComponent.Mixin._createContentMarkup (C:\sports-whole-page\node_modules\react\lib\ReactDOMComponent.js:591:32)
      at ReactDOMComponent.Mixin.mountComponent (C:\sports-whole-page\node_modules\react\lib\ReactDOMComponent.js:479:29)
      at Object.ReactReconciler.mountComponent (C:\sports-whole-page\node_modules\react\lib\ReactReconciler.js:37:35)
      at [object Object].ReactCompositeComponentMixin.mountComponent (C:\sports-whole-page\node_modules\react\lib\ReactCompositeComponent.js:225:34)
      at [object Object].wrapper [as mountComponent] (C:\sports-whole-page\node_modules\react\lib\ReactPerf.js:66:21)
      at Object.ReactReconciler.mountComponent (C:\sports-whole-page\node_modules\react\lib\ReactReconciler.js:37:35)
      at [object Object].ReactCompositeComponentMixin.mountComponent (C:\sports-whole-page\node_modules\react\lib\ReactCompositeComponent.js:225:34)
      at [object Object].wrapper [as mountComponent] (C:\sports-whole-page\node_modules\react\lib\ReactPerf.js:66:21)
      at Object.ReactReconciler.mountComponent (C:\sports-whole-page\node_modules\react\lib\ReactReconciler.js:37:35)
      at mountComponentIntoNode (C:\sports-whole-page\node_modules\react\lib\ReactMount.js:266:32)
      at ReactReconcileTransaction.Mixin.perform (C:\sports-whole-page\node_modules\react\lib\Transaction.js:136:20)
      at batchedMountComponentIntoNode (C:\sports-whole-page\node_modules\react\lib\ReactMount.js:282:15)
      at ReactDefaultBatchingStrategyTransaction.Mixin.perform (C:\sports-whole-page\node_modules\react\lib\Transaction.js:136:20)
      at Object.ReactDefaultBatchingStrategy.batchedUpdates (C:\sports-whole-page\node_modules\react\lib\ReactDefaultBatchingStrategy.js:62:19)
      at Object.batchedUpdates (C:\sports-whole-page\node_modules\react\lib\ReactUpdates.js:94:20)
      at Object.ReactMount._renderNewRootComponent (C:\sports-whole-page\node_modules\react\lib\ReactMount.js:476:18)
      at Object.wrapper [as _renderNewRootComponent] (C:\sports-whole-page\node_modules\react\lib\ReactPerf.js:66:21)
      at Object.ReactMount._renderSubtreeIntoContainer (C:\sports-whole-page\node_modules\react\lib\ReactMount.js:550:32)
      at Object.ReactMount.render (C:\sports-whole-page\node_modules\react\lib\ReactMount.js:570:23)
      at Object.wrapper [as render] (C:\sports-whole-page\node_modules\react\lib\ReactPerf.js:66:21)
      at Object.ReactTestUtils.renderIntoDocument (C:\sports-whole-page\node_modules\react\lib\ReactTestUtils.js:76:21)
      at Context.<anonymous> (C:/codebase/sports-whole-page/test/components/sports-top-portion/sports-top-portion-unit-tests.js:28:41)
      at callFn (C:\sports-whole-page\node_modules\mocha\lib\runnable.js:286:21)
      at Test.Runnable.run (C:\sports-whole-page\node_modules\mocha\lib\runnable.js:279:7)
      at Runner.runTest (C:\sports-whole-page\node_modules\mocha\lib\runner.js:421:10)
      at C:\sports-whole-page\node_modules\mocha\lib\runner.js:528:12
      at next (C:\sports-whole-page\node_modules\mocha\lib\runner.js:341:14)
      at C:\sports-whole-page\node_modules\mocha\lib\runner.js:351:7
      at next (C:\sports-whole-page\node_modules\mocha\lib\runner.js:283:14)
      at Immediate._onImmediate (C:\sports-whole-page\node_modules\mocha\lib\runner.js:319:5)

जवाबों:


182

यह बहुत आसान है। आप कॉल करके उत्पन्न रैपर घटक का परीक्षण करने का प्रयास कर रहे हैं connect()(MyPlainComponent)। उस रैपर घटक से एक Redux स्टोर तक पहुंच की उम्मीद है। आम तौर पर वह स्टोर इस रूप में उपलब्ध है context.store, क्योंकि आपके घटक पदानुक्रम के शीर्ष पर आपके पास एक होगा <Provider store={myStore} />। हालाँकि, आप अपने कनेक्टेड घटक को अपने आप से, बिना किसी स्टोर के प्रदान कर रहे हैं, इसलिए यह एक त्रुटि है।

आपको कुछ विकल्प मिले हैं:

  • एक स्टोर बनाएं और <Provider>अपने जुड़े घटक के आसपास रेंडर करें
  • एक स्टोर बनाएं और सीधे इसे पास करें <MyConnectedComponent store={store} />, क्योंकि जुड़ा हुआ घटक एक प्रोप के रूप में "स्टोर" भी स्वीकार करेगा
  • जुड़े घटक के परीक्षण को परेशान न करें। "सादे", असंबद्ध संस्करण का निर्यात करें, और इसके बजाय परीक्षण करें। यदि आप अपने सादे घटक और अपने mapStateToPropsकार्य का परीक्षण करते हैं , तो आप सुरक्षित रूप से मान सकते हैं कि जुड़ा हुआ संस्करण सही ढंग से काम करेगा।

आप शायद Redux डॉक्स में "परीक्षण" पृष्ठ के माध्यम से पढ़ना चाहते हैं: https://redux.js.org/recipes/writing-tests

संपादित करें :

वास्तव में यह देखने के बाद कि आपने स्रोत को पोस्ट किया है, और त्रुटि संदेश को फिर से पढ़ना है, असली समस्या स्पोर्ट्सटॉपन घटक के साथ नहीं है। समस्या यह है कि आप SportsTopPane को "पूरी तरह से" रेंडर करने की कोशिश कर रहे हैं, जो पहले के मामले में "उथले" रेंडर करने के बजाय अपने सभी बच्चों को प्रदान करता है। लाइन searchComponent = <SportsDatabase sportsWholeFramework="desktop" />;एक घटक प्रदान कर रही है जो मुझे लगता है कि यह भी जुड़ा हुआ है, और इसलिए एक दुकान को रिएक्ट के "संदर्भ" सुविधा में उपलब्ध होने की उम्मीद है।

इस बिंदु पर, आपके पास दो नए विकल्प हैं:

  • केवल SportsTopPane का "उथला" प्रतिपादन करें, ताकि आप इसे अपने बच्चों को पूरी तरह से प्रस्तुत करने के लिए मजबूर न करें
  • यदि आप SportsTopPane का "गहरा" प्रतिपादन करना चाहते हैं, तो आपको संदर्भ में एक Redux स्टोर प्रदान करना होगा। मेरा सुझाव है कि आप एनजाइम परीक्षण पुस्तकालय पर एक नज़र डालें, जो आपको वास्तव में ऐसा करने की अनुमति देता है। एक उदाहरण के लिए http://airbnb.io/enzyme/docs/api/ReactWrapper/setContext.html देखें ।

कुल मिलाकर, मैं यह नोट करूंगा कि आप इस एक घटक में बहुत अधिक करने की कोशिश कर रहे हैं और हो सकता है कि इस घटक के कम तर्क के साथ इसे छोटे टुकड़ों में तोड़ने पर विचार करें।


मैंने कोशिश की, लेकिन यह सुनिश्चित नहीं किया कि कैसे करना है ... क्या आप मेरे परीक्षण के मामलों में अपडेट कर सकते हैं

1
मैं मान रहा हूं कि SportsTopPortion.js में, आपके पास है let SportsTopPortion = connect(mapStateToProps)(SomeOtherComponent)। सबसे आसान उत्तर यह परीक्षण करना है कि अन्य घटक, घटक द्वारा नहीं लौटा है connect
मार्करसन

1
अहा। अब मैं देख रहा हूं कि क्या हो रहा है। समस्या SportsTopPane के साथ ही नहीं है। समस्या यह है कि आप SportsTopPane के "पूर्ण" रेंडर कर रहे हैं, न कि "उथले" रेंडर, इसलिए रिएक्ट पूरी तरह से सभी बच्चों को रेंडर करने की कोशिश कर रहा है। त्रुटि संदेश लाइन को संदर्भित करता है searchComponent = <SportsDatabase sportsWholeFramework="desktop" />;यह जुड़ा हुआ घटक है जो स्टोर और ब्रेकिंग की उम्मीद कर रहा है। तो, दो नए सुझाव: या तो केवल SportsTopPane के उथले प्रतिपादन, या परीक्षण करने के लिए एंजाइम की तरह एक पुस्तकालय का उपयोग करें। Airbnb.io/enzyme/docs/api/ReactWrapper/setContext.html देखें ।
मार्करसन

क्या आप मुझे बता सकते हैं कि इस परिदृश्य के लिए टेस्ट केस कैसे लिखें `` `लेकिन यह सुनिश्चित नहीं करें कि यूनिट टेस्ट के मामलों को कैसे लिखा जाए जब उसके मोबाइल को नेवी कंपोनेंट को रेंडर नहीं करना चाहिए। `` `

3
"जो बहुत सरल है" वाक्यांश के साथ फंस गया है या हैरान है, उसका उत्तर देना असंगत या कठोर हो सकता है। कृपया संयम से उपयोग करें।
jayqui

97

संभव समाधान जो मेरे साथ काम किया जेस्ट

import React from "react";
import { shallow } from "enzyme";
import { Provider } from "react-redux";
import configureMockStore from "redux-mock-store";
import TestPage from "../TestPage";

const mockStore = configureMockStore();
const store = mockStore({});

describe("Testpage Component", () => {
    it("should render without throwing an error", () => {
        expect(
            shallow(
                <Provider store={store}>
                    <TestPage />
                </Provider>
            ).exists(<h1>Test page</h1>)
        ).toBe(true);
    });
});

1
एक के बाद एक, प्रॉपर पास करने के बजाय अच्छी तरह से काम करता है।
भूतकृविज

2
धन्यवाद, एक बहुत अच्छा समाधान। मुझे यह समस्या थी क्योंकि मैं रूटिंग के साथ एक शीर्ष-स्तरीय ऐप घटक का उपयोग कर रहा हूं और स्टोर प्रत्येक मार्ग में चाइल्ड ऐप को प्रदान किया जाता है, इसलिए मुझे राउटर में प्रॉपर पास करने की आवश्यकता नहीं है। मैंने इसे अपने उपयोग के लिए थोड़ा बदल दिया। const आवरण = उथला (<प्रदाता की दुकान = {दुकान}> <App /> </ प्रदाता>); उम्मीद (आवरण); अनुरक्षण (<App />)).toBe(true);
थोड़ा मस्तिष्क

69

जैसा कि रिडक्स के आधिकारिक डॉक्स का सुझाव है, असंबद्ध घटक को भी निर्यात करना बेहतर है।

डेकोरेटर से निपटने के लिए बिना ऐप कंपोनेंट का परीक्षण करने में सक्षम होने के लिए, हम आपको अनिर्दिष्ट परिणाम का निर्यात करने की सलाह देते हैं:

import { connect } from 'react-redux'

// Use named export for unconnected component (for tests)
export class App extends Component { /* ... */ }// Use default export for the connected component (for app)
export default connect(mapStateToProps)(App)

चूंकि डिफ़ॉल्ट निर्यात अभी भी सजा हुआ घटक है, ऊपर चित्रित आयात विवरण पहले की तरह काम करेगा, इसलिए आपको अपना एप्लिकेशन कोड नहीं बदलना होगा। हालाँकि, अब आप अपनी परीक्षण फ़ाइल में अनिर्दिष्ट ऐप घटकों को इस तरह आयात कर सकते हैं:

// Note the curly braces: grab the named export instead of default export
import { App } from './App'

और अगर आपको दोनों की आवश्यकता है:

import ConnectedApp, { App } from './App'

एप्लिकेशन में ही, आप अभी भी इसे सामान्य रूप से आयात करेंगे:

import App from './App'

आप केवल परीक्षण के लिए नामित निर्यात का उपयोग करेंगे।


1
यह उत्तर कानूनी भी है। लंगर से मिलान करने के लिए अपने लिंक को संपादित किया।
Erowlin

यह जवाब सही समझ में आता है! यह सभी मामलों में सही बात नहीं हो सकती है, लेकिन प्रदाता के साथ खेलने की तुलना में निश्चित रूप से बेहतर है और जब यह आवश्यक नहीं है।
लोकोरी

धन्यवाद @lokori आपको यह पसंद आया!
विशाल गुलाटी

2
यह मेरे परीक्षण को फिर से पास करने का सबसे तेज और सरल तरीका था।
माइक

2
"आप केवल परीक्षण के लिए नामित निर्यात का उपयोग करेंगे।" -- मेरे लिये कार्य करता है।
टेक्नाज़ी

7

जब हम एक प्रतिक्रिया-रिडक्स एप्लिकेशन को एक साथ रखते हैं तो हमें एक संरचना देखने की उम्मीद करनी चाहिए जहां शीर्ष पर हमारे पास Providerटैग है जिसमें एक रिड्यूक्स स्टोर का एक उदाहरण है।

यह Providerटैग तब आपके मूल घटक को प्रस्तुत करता है, इसे वह Appघटक कहता है जो बदले में अनुप्रयोग के अंदर हर दूसरे घटक को प्रस्तुत करता है।

यहाँ मुख्य भाग है, जब हम connect()फ़ंक्शन के साथ एक घटक लपेटते हैं , तो यह connect()फ़ंक्शन पदानुक्रम के भीतर कुछ मूल घटक को देखने की उम्मीद करता है जिसमें Providerटैग होता है।

तो आप जिस connect()फंक्शन में फंक्शन डालते हैं , वह पदानुक्रम को देखेगा और खोजने की कोशिश करेगा Provider

आप जो चाहते हैं, वह हो जाता है, लेकिन आपके परीक्षण वातावरण में प्रवाह टूट रहा है।

क्यों?

क्यों?

जब हम ग्रहण किए गए sportsDatabase परीक्षण फ़ाइल पर वापस जाते हैं, तो आपको अपने आप ही sportsDatabase घटक होना चाहिए और फिर उस घटक को अलगाव में प्रस्तुत करने का प्रयास करना चाहिए।

तो अनिवार्य रूप से आप उस परीक्षण फ़ाइल के अंदर क्या कर रहे हैं, बस उस घटक को ले जा रहा है और बस इसे जंगली में फेंक रहा है और इसके Providerऊपर किसी भी स्टोर या स्टोर से कोई संबंध नहीं है और यही कारण है कि आप इस संदेश को देख रहे हैं।

Providerउस घटक के संदर्भ या प्रस्ताव में स्टोर या टैग नहीं है और इसलिए घटक एक त्रुटि फेंकता है क्योंकि यह Providerअपने माता-पिता के पदानुक्रम में एक टैग या स्टोर देखना चाहता है ।

तो उस त्रुटि का मतलब है।


6

मेरे मामले में बस

const myReducers = combineReducers({
  user: UserReducer
});

const store: any = createStore(
  myReducers,
  applyMiddleware(thunk)
);

shallow(<Login />, { context: { store } });


2

जूस इस आयात को "एंजाइम" से {उथले, माउंट} करते हैं;

const store = mockStore({
  startup: { complete: false }
});

describe("==== Testing App ======", () => {
  const setUpFn = props => {
    return mount(
      <Provider store={store}>
        <App />
      </Provider>
    );
  };

  let wrapper;
  beforeEach(() => {
    wrapper = setUpFn();
  });

2

मेरे लिए यह आयात मुद्दा था, आशा है कि यह मदद करेगा। WebStorm द्वारा डिफ़ॉल्ट आयात गलत था।

बदलने के

import connect from "react-redux/lib/connect/connect";

साथ में

import {connect} from "react-redux";

1

मेरे अपग्रेड होने पर यह हुआ। मुझे वापस नीचे जाना पड़ा।

प्रतिक्रिया-रिडक्स ^ 5.0.6 → ^ 7.1.3


यह एक asnwer की तुलना में अधिक है
sudo97

बहुत सारे परिवर्तन हुए। मैं इस वीडियो को बेहतर तरीके से समझने के लिए देखने की सलाह देता हूं। youtube.com/watch?v=yOZ4Ml9LlWE
Kamil Dzieniszewski

0

अपने Index.js के अंत में इस कोड को जोड़ने की आवश्यकता है:

import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter  } from 'react-router-dom';

import './index.css';
import App from './App';

import { Provider } from 'react-redux';
import { createStore, applyMiddleware, compose, combineReducers } from 'redux';
import thunk from 'redux-thunk';

///its your redux ex
import productReducer from './redux/reducer/admin/product/produt.reducer.js'

const rootReducer = combineReducers({
    adminProduct: productReducer
   
})
const composeEnhancers = window._REDUX_DEVTOOLS_EXTENSION_COMPOSE_ || compose;
const store = createStore(rootReducer, composeEnhancers(applyMiddleware(thunk)));


const app = (
    <Provider store={store}>
        <BrowserRouter   basename='/'>
            <App />
        </BrowserRouter >
    </Provider>
);
ReactDOM.render(app, document.getElementById('root'));

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.